{"id":9740,"date":"2020-10-01T09:17:17","date_gmt":"2020-10-01T07:17:17","guid":{"rendered":"https:\/\/sii.pl\/blog\/?p=9740"},"modified":"2023-05-08T15:12:40","modified_gmt":"2023-05-08T13:12:40","slug":"caching-w-asp-net-core-3-1-oraz-redis","status":"publish","type":"post","link":"https:\/\/sii.pl\/blog\/caching-w-asp-net-core-3-1-oraz-redis\/","title":{"rendered":"Caching w ASP .Net Core 3.1 oraz Redis"},"content":{"rendered":"\n<p>Ostatnio dosta\u0142em kilka projekt\u00f3w \u2013 w kilku z nich mia\u0142em wprowadzi\u0107 caching z powodu d\u0142ugiego oczekiwania na odpowied\u017a z serwera. W innych przypadkach \u2013 zacz\u0105\u0142em testowa\u0107 zu\u017cycie pami\u0119ci i procesora, d\u0142ugo\u015b\u0107 odpowiedzi i cz\u0119stotliwo\u015b\u0107 pobierania tych samych danych z bazy. W konsekwencji, zaproponowane przeze mnie dodanie cachingu do aplikacji i zalety z tego p\u0142yn\u0105ce zosta\u0142y ch\u0119tnie przyj\u0119te jako dalsze zadania do zrealizowania w projekcie. Mimo zwi\u0119kszonego bud\u017cetu projekt\u00f3w ko\u0144cowy efekt by\u0142 lepszy dla biznesu i dawa\u0142o to wi\u0119ksze zadowolenie klienta.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Caching<\/h2>\n\n\n\n<p>Czym jest Caching? Jest to technika przechowywania cz\u0119sto pobieranych danych z wolniejszych medi\u00f3w na szybsze. Dane te pobierane s\u0105 np. z bazy danych, dysku lub innego magazynu i zapisane do miejsca, kt\u00f3re jest szybsze \u2013 najcz\u0119\u015bciej jest to pami\u0119ci RAM. Dzi\u0119ki temu odpowiedzi na zapytania do serwera mog\u0105 zosta\u0107 obs\u0142u\u017cone szybciej i zu\u017cy\u0107 mniej mocy operacyjnej.<\/p>\n\n\n\n<p>Dane w projektach mo\u017cemy przechowywa\u0107 zar\u00f3wno po stronie klienta w przegl\u0105darce\/aplikacji oraz po stronie serwera.<\/p>\n\n\n\n<p>Po stronie przegl\u0105darki dane mo\u017cemy zapisywa\u0107 do localStorage, sessionStorage lub IndexedDB dla wi\u0119kszej ilo\u015bci danych. Taki ich zapis ma swoje zalety, ale tak\u017ce wady. Odci\u0105\u017cony b\u0119dzie serwer aplikacji, minusem jednak b\u0119dzie zaistnienie sytuacji, w kt\u00f3rej u\u017cytkownik posiada ju\u017c przestarza\u0142e dane.<\/p>\n\n\n\n<p>W tym artykule skupimy si\u0119 na cachingu po stronie serwera w technologii ASP .NET Core. Wyr\u00f3\u017cniamy w niej dwa typy przechowywania danych:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>In-Memory Caching \u2013 dane s\u0105 zachowane w pami\u0119ci RAM w serwerze i przypisane do procesu aplikacji,<\/li><li>Distributed Caching \u2013 dane s\u0105 zachowane na zewn\u0119trznym serwerze.<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Caching w projektach C#<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">In-Memory Cache<\/h3>\n\n\n\n<p>U\u017cycie klasy MemoryCache w ASP .Net Core wymaga instalacji pakietu Microsoft.Extensions.Caching.Memory do projektu. Pozwala on na \u0142atwy dost\u0119p do pami\u0119ci RAM komputera, zapis i odczyt do niej. Nale\u017cy pami\u0119ta\u0107 \u017ce mamy dost\u0119p tylko do obszaru przydzielonego dla naszego procesu.<\/p>\n\n\n\n<p>Aby m\u00f3c wykorzysta\u0107 klas\u0119 MemoryCache w programie, nale\u017cy w klasie Startup ustawi\u0107:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npublic void ConfigureServices(IServiceCollection services)\n{\n    \u2026\n    services.AddMemoryCache();\n \n    \u2026\n}\n<\/pre><\/div>\n\n\n<p>W kontrolerze mo\u017cna u\u017cy\u0107 DI. Do tego nale\u017cy doda\u0107 w konstruktorze jako parametr IMemoryCache:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\u2026\nprivate IMemoryCache _cache;\n \npublic WeatherForecastController(IMemoryCache memoryCache)\n{\n    _cache = memoryCache;\n}\n\u2026\n<\/pre><\/div>\n\n\n<p>Przez konstruktor, w 4 linii, zostaje wstrzykni\u0119ty obiekt MemoryCache i zostaje on przypisany do prywatnej zmiennej klasy kontrolera.<\/p>\n\n\n\n<p>Przyk\u0142adowa implementacja w metodzie mo\u017ce wygl\u0105da\u0107 nast\u0119puj\u0105co:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n&#x5B;HttpGet]\npublic IEnumerable&lt;WeatherForecast&gt; Get()\n{\n    List&lt;WeatherForecast&gt; forecasts = null;\n \n    if (!_cache.TryGetValue(CacheKey, out forecasts))\n    {\n        forecasts = new ForecastService().GetForecast().ToList();\n \n        var cacheEntryOptions = new MemoryCacheEntryOptions()\n            .SetAbsoluteExpiration(TimeSpan.FromMinutes(15));\n \n        _cache.Set(CacheKey, forecasts.ToList(), cacheEntryOptions);\n    }\n \n    return forecasts;\n}\n<\/pre><\/div>\n\n\n<p>Nale\u017cy pami\u0119ta\u0107, \u017ce przy dobrej architekturze \u2013 takie operacje b\u0119d\u0105 znajdowa\u0107 si\u0119 w odpowiednich klasach serwisowych lub fabrykach.<\/p>\n\n\n\n<p>Implementacja wykonuje swoje zadanie. W metodzie Get najpierw jest zapytanie do pami\u0119ci o dane (w linii 6). W przypadku ich braku zostaj\u0105 one pobrane z serwisu (linia 7). W \u015brodku warunku zostaje utworzony obiekt z opcjami dla wpisu do MemoryCache, nast\u0119pnie jest on przypisany przez metod\u0119 Set.<\/p>\n\n\n\n<p>Problemem w powy\u017cszym kodzie jest safe-thread dla operacji odczytu i zapisu danych, aby temu zapobiec nale\u017cy u\u017cy\u0107 obiektu dla blokady operacji. Zostaje ona dodana do g\u0142\u00f3wnych zmiennych klasy jako statyczn\u0105.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n&#x5B;HttpGet]\npublic IEnumerable&lt;WeatherForecast&gt; Get()\n{\n    List&lt;WeatherForecast&gt; forecasts = null;\n  \n    _cache.TryGetValue(CacheKey, out forecasts);\n    if (forecasts != null)\n        return forecasts;\n  \n    lock (lockObject)\n    {\n        _cache.TryGetValue(CacheKey, out forecasts);\n  \n        if (forecasts == null)\n        {\n            forecasts = new ForecastService().GetForecast().ToList();\n            _cache.Set(CacheKey, forecasts.ToList(), TimeSpan.FromMinutes(15));\n        }\n    }\n  \n    return forecasts;\n}\n<\/pre><\/div>\n\n\n<p>W liniach od 4 do 6 jest pr\u00f3ba odczytania danych z pami\u0119ci Cache, nast\u0119pnie zwr\u00f3ci\u0107 je je\u015bli istniej\u0105, je\u015bli nie \u2013 w linii 8 obiekt (lockObject) jest blokowany. W \u015brodku zostaje jeszcze raz sprawdzone czy inny w\u0105tek doda\u0142 ju\u017c dane do pami\u0119ci. Nast\u0119pnie dane s\u0105 pobierane z serwisu (linia 14) i zapisane do Cache\u2019a (linia 15).<\/p>\n\n\n\n<p>Plusy rozwi\u0105zania przechowywania danych przez klas\u0119 MemoryCache to:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>\u0141atwa implementacja (nie wliczaj\u0105c w to blokad na bezpiecze\u0144stwo w\u0105tk\u00f3w),<\/li><li>dobrze pasuj\u0105ce do ma\u0142ych aplikacji,<\/li><li>nie s\u0105 wymagane dodatkowe serwery czy ich ustawienia.<\/li><\/ul>\n\n\n\n<p>In-Memory Cache posiada tak\u017ce swoje wady, kt\u00f3rymi s\u0105 :<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Konieczno\u015b\u0107 limitowania i poprawnego konfigurowania ilo\u015bci zaj\u0119tej pami\u0119ci, w przeciwnym przypadku pami\u0119\u0107 serwera mo\u017ce zosta\u0107 szybko zaj\u0119ta i spowolni to aplikacj\u0119 zamiast j\u0105 przyspieszy\u0107,<\/li><li>ma\u0142e mo\u017cliwo\u015bci podczas skalowania aplikacji,<\/li><li>domy\u015blna implementacja (nawet z metod\u0105 GetOrCreate) ma problemy z Thread-safe i mo\u017ce powodowa\u0107 niechciane skutki uboczne<a href=\"#_ftn1\" name=\"_ftnref1\" rel=\"nofollow\" >[1]<\/a>,<\/li><li>czyszczenie danych w pami\u0119ci podczas resetu lub zamkni\u0119cia procesu lub aplikacji,<\/li><li>mo\u017ce wymaga\u0107 zwi\u0119kszenia pami\u0119ci RAM \u2013 w konsekwencji np. wykupienia dro\u017cszego planu serwera tylko ze wzgl\u0119du na Cache.<\/li><\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">LazyCache.AspNetCore<\/h3>\n\n\n\n<p>LazyCache to biblioteka dla tworzenia cachingu. Zapewnia ona prosty dost\u0119p do pami\u0119ci oraz jest bezpieczna w\u0105tkowo (ang. <em>Thread-Safe<\/em>). Biblioteka LazyCache.AspNetCore zapewnia dodatkowy interfejs IAppCache, kt\u00f3ry dostarcza mo\u017cliwo\u015b\u0107 wstrzykni\u0119cia serwisu w kontroler lub inn\u0105 docelow\u0105 klas\u0119. Biblioteka ta korzysta z bibliotek .Net MemoryCache oraz Lazy.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npublic void ConfigureServices(IServiceCollection services)\n{\n    \u2026\n    services.AddLazyCache();\n    \u2026\n}\n<\/pre><\/div>\n\n\n<p>Na powy\u017cszym listingu kodu pokazana zosta\u0142a rejestracja LazyCache, dzi\u0119ki kt\u00f3rej serwis <strong>CachingService <\/strong>b\u0119dzie dost\u0119pny wsz\u0119dzie w kodzie za pomoc\u0105 DI przez interfejs IAppCache.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\u2026\nprivate readonly IAppCache _cache;\n  \npublic WeatherForecastController(IAppCache cache)\n{\n    _cache = cache;\n}\n\u2026\n<\/pre><\/div>\n\n\n<p>W linii 4 powy\u017cszego kodu wstrzykujemy serwis <strong>CachingService<\/strong> do kontrolera <strong>WeatherForecast<\/strong>, do prywatnej zmiennej <strong>_cache<\/strong>.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\u2026\n&#x5B;HttpGet]\npublic IEnumerable&lt;WeatherForecast&gt; Get()\n{\n    return _cache.GetOrAdd(CacheKey, () =&gt; new ForecastService().GetForecast().ToList(), TimeSpan.FromMinutes(15));\n}\n\u2026\n<\/pre><\/div>\n\n\n<p>Na powy\u017cszym przyk\u0142adzie ukazany jest przyk\u0142ad u\u017cycia wcze\u015bniej wstrzykni\u0119tego serwisu. W 5 linii kodu u\u017cyta jest metoda <strong>GetOrAdd <\/strong>przyjmuj\u0105ca trzy parametry, s\u0105 to:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Unikalny klucz w pami\u0119ci dla danych,<\/li><li>funkcja lambda, kt\u00f3ra ma zosta\u0107 wykonana przy braku danych do ich pobrania i wstawienia do listy,<\/li><li>znacznik czasu, kt\u00f3ry wskazuje przez jaki czas maj\u0105 by\u0107 dane przechowywane w pami\u0119ci podr\u0119cznej serwera.<\/li><\/ul>\n\n\n\n<p>Jak w poprzednim przyk\u0142adzie \u2013 wady i zalety pokrywaj\u0105 si\u0119 z wyj\u0105tkiem \u0142atwiejszej implementacji dla wielow\u0105tkowej aplikacji. Takie rozwi\u0105zanie nadal jest przydatne tylko przy mniej wymagaj\u0105cych aplikacjach, kt\u00f3re s\u0105 u\u017cywane g\u0142\u00f3wnie w biznesie np. w jednej firmie lub przy ograniczonej liczbie u\u017cytkownik\u00f3w.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Redis<\/h3>\n\n\n\n<p>Redis jest u\u017cywana jako baza danych, cache i po\u015brednik wiadomo\u015bci, kt\u00f3rych struktura danych jest przechowywana w pami\u0119ci. Zapewnia ona aplikacjom dost\u0119p do danych o du\u017cej przepustowo\u015bci i z ma\u0142ymi op\u00f3\u017anieniami.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Konfiguracja Redis na Azure oraz Docker<\/h3>\n\n\n\n<p>Utworzenie instancji na portalu Azure jest bardzo proste.<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Nale\u017cy wej\u015b\u0107 na odpowiedn\u0105 subskrypcj\u0119 na portalu Azure,<\/li><li>wyszuka\u0107 w marketplace <strong>Azure Cache for Redis<\/strong>,<\/li><li>wype\u0142ni\u0107 wymagaj\u0105ce pola m.in. nazwa, subskrypcj\u0119, warstw\u0119 cenow\u0105 etc. i klikn\u0105\u0107 przycisk utw\u00f3rz.<\/li><\/ol>\n\n\n\n<p>Potrzebny connection string znajdziemy w sekcji <strong>Settings -&gt; Access keys<\/strong>, znajduje si\u0119 tam wpis <strong>Primary<\/strong> <strong>connection string<\/strong> (mo\u017cna u\u017cy\u0107 tak\u017ce <strong>Secondary<\/strong>). Nale\u017cy go skopiowa\u0107 do pliku appsettings.json, kt\u00f3ry finalnie powinien go w sobie zawiera\u0107 i wygl\u0105da\u0107 w nast\u0119puj\u0105cy spos\u00f3b:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n{\n  &quot;ConnectionString&quot;: &quot;&lt;cache name&gt;.redis.cache.windows.net,abortConnect=false,ssl=true,password=&lt;primary-access-key&gt;&quot;\n}\n<\/pre><\/div>\n\n\n<p>Niestety sama instrancja Redisa jest ju\u017c p\u0142atna (na chwile obecn\u0105 brak darmowej). Dla programist\u00f3w mo\u017ce by\u0107 pomocne u\u017cycie obrazu z Dockera na systemie Windows 10. Nale\u017cy zainstalowa\u0107 Docker Desktop, nast\u0119pnie uruchomi\u0107 nast\u0119puj\u0105c\u0105 komend\u0119 w konsoli:<\/p>\n\n\n\n<p>docker run &#8211;name myredis -p 6379:6379 redis<\/p>\n\n\n\n<p>Gdzie:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>run \u2013 uruchamia obraz,<\/li><li>&#8211;name myredis \u2013 tak nazywa si\u0119 instancja Redisa,<\/li><li>-p 6379:6379 &#8211; otwiera port,<\/li><li>redis \u2013 nazwa obrazu w Docker Hub do pobrania i uruchomienia.,<\/li><\/ul>\n\n\n\n<p>Lokalny connection string b\u0119dzie wygl\u0105da\u0142 nast\u0119puj\u0105co:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\"127.0.0.1:6379\"\n<\/pre><\/div>\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\u2026\npublic void ConfigureServices(IServiceCollection services)\n{\n  \n    \u2026\n    services.AddStackExchangeRedisCache(options =&gt; options.Configuration = Configuration.GetValue&lt;string&gt;(&quot;ConnectionString&quot;));\n    \u2026\n}\n\u2026\n<\/pre><\/div>\n\n\n<p>Powy\u017cej znajduje si\u0119 konfiguracja serwis\u00f3w w ASP. Net Core, kt\u00f3ra ma za zadanie dodanie obs\u0142ugi wstrzykiwania serwis\u00f3w Redisa poprzez interfejs <strong>IDistributedCache<\/strong>.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\u2026\nprivate readonly IDistributedCache distributedCache;\n \npublic WeatherForecastController(IDistributedCache distributedCache)\n{\n    this.distributedCache = distributedCache;\n}\n\u2026\n&#x5B;HttpPost]\npublic async Task&lt;IEnumerable&lt;WeatherForecast&gt;&gt; GetAsync()\n{\n    var redisCustomerList = await distributedCache.GetStringAsync(CacheKey);\n    if (redisCustomerList != null)\n    {\n        return JsonConvert.DeserializeObject&lt;List&lt;WeatherForecast&gt;&gt;(redisCustomerList);\n    }\n    else\n    {\n        var dbWeatherForecasts = new ForecastService().GetForecast().ToList();\n        var serializedDbWeatherForecasts = JsonConvert.SerializeObject(dbWeatherForecasts);\n  \n        var options = new DistributedCacheEntryOptions()\n            .SetAbsoluteExpiration(DateTime.Now.AddMinutes(10));\n  \n        await distributedCache.SetStringAsync(CacheKey, serializedDbWeatherForecasts, options);\n  \n        return dbWeatherForecasts;\n    }\n}\n\u2026\n<\/pre><\/div>\n\n\n<p>W linii 5 zostaje przypisany wcze\u015bniej wstrzykni\u0119ty serwis Redisa do prywatnej zmiennej. W funkcji <strong>GetAsync<\/strong> sprawdzana jest najpierw zawarto\u015b\u0107 klucza na serwerze cachingu. Je\u015bli jest \u2013 zostaje to deserializowane na obiekty i zwr\u00f3cone do klienta. Je\u017celi nadal ich brakuje w pami\u0119ci zostaj\u0105 ona pobrane z pami\u0119ci, a nast\u0119pnie serializowane i dodane za pomoc\u0105 serwisu w linii 23. Zostaj\u0105 tak\u017ce dodane opcje <strong>DistributedCacheEntryOptions,<\/strong> kt\u00f3re powoduj\u0105 skasowanie danych po 10 minutach.<\/p>\n\n\n\n<p>Zaletami tego rozwi\u0105zania jest osobny serwer z pami\u0119ci\u0105 wykorzystan\u0105 g\u0142\u00f3wnie do przechowywania danych i daj\u0105c\u0105 du\u017c\u0105 przewag\u0119 nad poprzednimi rozwi\u0105zaniami. Mo\u017cna wykorzysta\u0107 instancj\u0119 Redisa tak\u017ce do innych web aplikacji.<\/p>\n\n\n\n<p>U\u017cycie Redisa ma swoje wady, g\u0142\u00f3wn\u0105 z nich jest potrzeba po\u0142\u0105czenia si\u0119 z serwerem i czekanie na odpowied\u017a. Powinno si\u0119 w tej sytuacji tworzy\u0107 docelowe serwery (zar\u00f3wno na instancj\u0119 do cachingu i web aplikacji) w tych samych regionach. Brakuje tak\u017ce wbudowanych mechanizm\u00f3w zapobiegania nadpisywania i ponownego pobierania danych przez serwisy.<\/p>\n\n\n\n<p>Redis nadaje si\u0119 ju\u017c do wi\u0119kszych projekt\u00f3w oraz takich, kt\u00f3re wymagaj\u0105 rozproszonego systemu przechowywania danych do szybkiego dost\u0119pu.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Dodatki<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Problem z wydajno\u015bci\u0105 serializacji danych<\/h3>\n\n\n\n<p>Serializacje danych, kt\u00f3re zostan\u0105 zapisane do pami\u0119ci cache, spowoduje kolejny spadek wydajno\u015bci serwer\u00f3w, kt\u00f3ry mo\u017ce zwi\u0119kszy\u0107 nasz czas odpowiedzi do klienta. Najlepszym sposobem na to jest napisanie w\u0142asnego adaptera do serializacji danych, dok\u0142adne oznaczenie w\u0142a\u015bciwo\u015bci jak maj\u0105 by\u0107 dodane do pami\u0119ci (czy w og\u00f3le maj\u0105 zosta\u0107 tam zapisa\u0107). Dobrze tak\u017ce przejrze\u0107 benchmarki znanych lub nowych bibliotek do tego napisanych. Np. na tej <a href=\"https:\/\/aloiskraus.wordpress.com\/2019\/09\/29\/net-serialization-benchmark-2019-roundup\/\" rel=\"nofollow\" >stronie<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Publisher-Subscriber<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nprivate const string Topic = &quot;MyTopic&quot;;\n \nprivate static void Subscriber(ConnectionMultiplexer connection)\n{\n    var pubsub = connection.GetSubscriber();\n  \n    pubsub.Subscribe(Topis, (channel, message) =&gt; Console.WriteLine(message));\n}\n  \nprivate static void Publisher(ConnectionMultiplexer connection)\n{\n    var pubsub = connection.GetSubscriber();\n  \n    pubsub.Publish(Topis, DateTime.Now.ToString());\n}\n<\/pre><\/div>\n\n\n<p>Redis posiada tak\u017ce mo\u017cliwo\u015b\u0107 utworzenia wzorca Publish-Subscriber. Mo\u017cna utworzy\u0107 jeden temat, do kt\u00f3rego podpina si\u0119 kilka klient\u00f3w i go subskrybuje. Kiedy zostanie wys\u0142ana wiadomo\u015b\u0107 do tematu, zostaje ona wys\u0142ana do subskrybent\u00f3w i obs\u0142u\u017cona przez nich. Mog\u0105 to by\u0107 np. zmiany na \u017cywo w po\u015bcie, live coding, czat czy powiadamianie o zmianach.<\/p>\n\n\n\n<p>Z ConnectionMultiplexer zostaje pobrany Subsriber. Nast\u0119pnie za pomoc\u0105 metod Publish mo\u017cna wys\u0142a\u0107 wiadomo\u015b\u0107 na dany temat. Metoda Subscribe pobiera przysz\u0142e komunikaty w temacie, drugi parametr jest to przypisana funkcja, kt\u00f3ra obs\u0142uguje wys\u0142an\u0105 wiadomo\u015b\u0107.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Batch operacje<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nprivate static void BatchOps(IDatabase cache)\n{\n    var taskList = new List&lt;Task&gt;();\n    var batch = cache.CreateBatch();\n    for (int i = 0; i &lt; 10; i++)\n        taskList.Add(batch.StringSetAsync($&quot;key{i}&quot;, i));\n  \n    batch.Execute();\n    Task.WaitAll(taskList.ToArray());\n}\n<\/pre><\/div>\n\n\n<p>Powy\u017cszy kod pokazuje w jaki spos\u00f3b mo\u017cemy stworzy\u0107 wiele kluczy naraz je\u015bli zajdzie taka potrzeba. Mo\u017ce to by\u0107 np. od\u015bwie\u017cenie wielu post\u00f3w, nawigacji czy profili u\u017cytkownik\u00f3w. W linii 4 tworzony jest obiekt <strong>Batch<\/strong>. Mo\u017cna na nim wykonywa\u0107 zwyk\u0142e operacje, tak jak na interfejsie IDistributedCache. Aby zosta\u0142y one wys\u0142ane do serwera nale\u017cy wywo\u0142a\u0107 metod\u0119 <strong>Execute<\/strong>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Cache-Aside<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\u2026\n&#x5B;HttpPost]\npublic async Task&lt;IActionResult&gt; SetAsync(IEnumerable&lt;WeatherForecast&gt; weatherForecasts)\n{\n    new ForecastService().SetForecast(weatherForecasts);\n    await distributedCache.RemoveAsync(CacheKey);\n  \n    return Ok();\n}\n\u2026\n<\/pre><\/div>\n\n\n<p>Wzorzec Cache-Aside polega na zapisaniu danych z serwisu do podr\u0119cznej pami\u0119ci RAM (lub innego szybkiego no\u015bnika). Kiedy nast\u0119puje kolejna pr\u00f3ba ich odczytania \u2013 sprawdzane jest czy istniej\u0105 one w pami\u0119ci \u2013 je\u015bli tak, to zostaj\u0105 one zwr\u00f3cone. Je\u015bli dane zostaj\u0105 zaktualizowane lub dodane, to s\u0105 one kasowane z pami\u0119ci cache.<\/p>\n\n\n\n<p>Powy\u017cej jest metoda, kt\u00f3ra zapisuje nowe dane do serwisu. Nast\u0119pnie ich stare wersje zostaj\u0105 skasowane z pami\u0119ci w 5 linii.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Update cache in the background<\/h3>\n\n\n\n<p>Warto przemy\u015ble\u0107 spraw\u0119 o aktualizacji instancji Redis lub wewn\u0119trznej pami\u0119ci Cache. Mo\u017ce by\u0107 w tym przypadku przydatny WebJob lub BackgroundService. Uruchomionie w nocy, wspomo\u017ce serwery w ci\u0105gu dnia z utrzymaniem dobrej wydajno\u015bci oraz szybkimi odpowiedziami.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Podsumowanie<\/h2>\n\n\n\n<p>W artykule zosta\u0142y przedstawione r\u00f3\u017cne sposoby implementacji buforowania danych po stronie serwera. Warto przeanalizowa\u0107 projekty i wymagania klienta przed dobraniem odpowiedniej opcji. Tak jak, dla mniejszych aplikacji, przy mniejszym ruchu u\u017cytkownik\u00f3w mo\u017ce wystarczy\u0107 standardowy MemoryCache. Przy du\u017cej infrastrukturze mo\u017ce by\u0107 ju\u017c potrzebny Redis oraz odpowiednia konfiguracja serwer\u00f3w instancji cachingu i aplikacji klienta.<\/p>\n\n\n\n<p><a name=\"_ftn1\" href=\"#_ftnref1\" rel=\"nofollow\" >[1]<\/a> <a href=\"https:\/\/blog.novanet.no\/asp-net-core-memory-cache-is-get-or-create-thread-safe\/\" rel=\"nofollow\" >https:\/\/blog.novanet.no\/asp-net-core-memory-cache-is-get-or-create-thread-safe\/<\/a><\/p>\n\n\n\n<p>***<\/p>\n\n\n\n<p>Je\u017celi interesuj\u0105 Ci\u0119 zagadnienia zwi\u0105zane z .net, polecamy r\u00f3wnie\u017c <a href=\"https:\/\/sii.pl\/blog\/wyszukiwarka\/.net\/\" target=\"_blank\" aria-label=\"inne artyku\u0142y naszych ekspert\u00f3w. (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\">inne artyku\u0142y naszych ekspert\u00f3w.<\/a> <\/p>\n\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;9740&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;9&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;4.3&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;11&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;4.3\\\/5 ( votes: 9)&quot;,&quot;size&quot;:&quot;18&quot;,&quot;title&quot;:&quot;Caching w ASP .Net Core 3.1 oraz Redis&quot;,&quot;width&quot;:&quot;119.2&quot;,&quot;_legend&quot;:&quot;{score}\\\/{best} ( {votes}: {count})&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 119.2px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 14.4px;\">\n            4.3\/5 ( votes: 9)    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>Ostatnio dosta\u0142em kilka projekt\u00f3w \u2013 w kilku z nich mia\u0142em wprowadzi\u0107 caching z powodu d\u0142ugiego oczekiwania na odpowied\u017a z serwera. &hellip; <a class=\"continued-btn\" href=\"https:\/\/sii.pl\/blog\/caching-w-asp-net-core-3-1-oraz-redis\/\">Continued<\/a><\/p>\n","protected":false},"author":241,"featured_media":9770,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_editorskit_title_hidden":false,"_editorskit_reading_time":6,"_editorskit_is_block_options_detached":false,"_editorskit_block_options_position":"{}","inline_featured_image":false,"footnotes":""},"categories":[1314],"tags":[357,976,975],"class_list":["post-9740","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development-na-twardo","tag-asp-net-core","tag-caching","tag-redis"],"acf":[],"aioseo_notices":[],"republish_history":[],"featured_media_url":"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2020\/09\/Caching-w-ASP-.Net-Core-3.1-oraz-Redis.png","category_names":["Development na twardo"],"_links":{"self":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/9740"}],"collection":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/users\/241"}],"replies":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/comments?post=9740"}],"version-history":[{"count":2,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/9740\/revisions"}],"predecessor-version":[{"id":21421,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/9740\/revisions\/21421"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/media\/9770"}],"wp:attachment":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/media?parent=9740"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/categories?post=9740"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/tags?post=9740"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}