.NET মেমোরি ক্যাশে যথাযথ ব্যবহারের জন্য লকিং প্যাটার্ন


115

আমি ধরে নিই এই কোডটিতে সামঞ্জস্যপূর্ণ সমস্যা রয়েছে:

const string CacheKey = "CacheKey";
static string GetCachedData()
{
    string expensiveString =null;
    if (MemoryCache.Default.Contains(CacheKey))
    {
        expensiveString = MemoryCache.Default[CacheKey] as string;
    }
    else
    {
        CacheItemPolicy cip = new CacheItemPolicy()
        {
            AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddMinutes(20))
        };
        expensiveString = SomeHeavyAndExpensiveCalculation();
        MemoryCache.Default.Set(CacheKey, expensiveString, cip);
    }
    return expensiveString;
}

সম্মতিযুক্ত ইস্যুর কারণ হ'ল একাধিক থ্রেড একটি নাল কী পেতে পারে এবং তারপরে ক্যাশে ডেটা toোকানোর চেষ্টা করতে পারে।

এই কোড একত্রীকরণের প্রমাণ তৈরি করার সবচেয়ে সংক্ষিপ্ততম এবং পরিষ্কার উপায় কী হবে? আমি আমার ক্যাশে সম্পর্কিত কোড জুড়ে একটি ভাল প্যাটার্ন অনুসরণ করতে চাই। একটি অনলাইন নিবন্ধের একটি লিঙ্ক একটি দুর্দান্ত সাহায্য হবে।

হালনাগাদ:

আমি @ স্কট চেম্বারলাইনের উত্তরের ভিত্তিতে এই কোডটি নিয়ে এসেছি। কেউ কি এর সাথে কোনও পারফরম্যান্স বা সম্মতিযুক্ত সমস্যা খুঁজে পেতে পারেন? যদি এটি কাজ করে, এটি কোড এবং ত্রুটির অনেক লাইন সংরক্ষণ করবে।

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Caching;

namespace CachePoc
{
    class Program
    {
        static object everoneUseThisLockObject4CacheXYZ = new object();
        const string CacheXYZ = "CacheXYZ";
        static object everoneUseThisLockObject4CacheABC = new object();
        const string CacheABC = "CacheABC";

        static void Main(string[] args)
        {
            string xyzData = MemoryCacheHelper.GetCachedData<string>(CacheXYZ, everoneUseThisLockObject4CacheXYZ, 20, SomeHeavyAndExpensiveXYZCalculation);
            string abcData = MemoryCacheHelper.GetCachedData<string>(CacheABC, everoneUseThisLockObject4CacheXYZ, 20, SomeHeavyAndExpensiveXYZCalculation);
        }

        private static string SomeHeavyAndExpensiveXYZCalculation() {return "Expensive";}
        private static string SomeHeavyAndExpensiveABCCalculation() {return "Expensive";}

        public static class MemoryCacheHelper
        {
            public static T GetCachedData<T>(string cacheKey, object cacheLock, int cacheTimePolicyMinutes, Func<T> GetData)
                where T : class
            {
                //Returns null if the string does not exist, prevents a race condition where the cache invalidates between the contains check and the retreival.
                T cachedData = MemoryCache.Default.Get(cacheKey, null) as T;

                if (cachedData != null)
                {
                    return cachedData;
                }

                lock (cacheLock)
                {
                    //Check to see if anyone wrote to the cache while we where waiting our turn to write the new value.
                    cachedData = MemoryCache.Default.Get(cacheKey, null) as T;

                    if (cachedData != null)
                    {
                        return cachedData;
                    }

                    //The value still did not exist so we now write it in to the cache.
                    CacheItemPolicy cip = new CacheItemPolicy()
                    {
                        AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddMinutes(cacheTimePolicyMinutes))
                    };
                    cachedData = GetData();
                    MemoryCache.Default.Set(cacheKey, cachedData, cip);
                    return cachedData;
                }
            }
        }
    }
}

3
আপনি কেন ব্যবহার ReaderWriterLockSlimকরবেন না ?
ডার্থভেডার

2
আমি ডার্থভেদারের সাথে একমত ... আমার মনে হয় আপনি ঝুঁকছেন ... ReaderWriterLockSlimতবে আমি বিবৃতি এড়াতে এই কৌশলটিও ব্যবহার করব try-finally
poy

1
আপনার আপডেট হওয়া সংস্করণটির জন্য, আমি আর কোনও একক ক্যাচলকে লক করব না, পরিবর্তে আমি প্রতি কীটি লক করব। এটি সহজেই Dictionary<string, object>এমনভাবে করা যেতে পারে যেখানে আপনি কীটি একই কী ব্যবহার করেন MemoryCacheএবং অভিধানে থাকা অবজেক্টটি কেবলমাত্র Objectআপনার লক করা একটি বেসিক । যাইহোক, যা বলা হচ্ছে, আমি আপনাকে জনা হানার উত্তরের মাধ্যমে পড়ার বিষয়টি স্মরণ করিয়ে দেব। যথাযথ প্রোফাইল না দিয়ে আপনি দুটি প্রোগ্রাম SomeHeavyAndExpensiveCalculation()চালানোর চেয়ে লকিংয়ের সাথে আপনার প্রোগ্রামটি কমিয়ে ফেলতে পারেন এবং এর একটি ফল ফেলে দেওয়া হতে পারে।
স্কট চেম্বারলাইন

1
আমার কাছে মনে হচ্ছে ক্যাশে ব্যয়বহুল মান পাওয়ার পরে ক্যাশেআইটেম পলিসি তৈরি করা আরও সঠিক হবে। সবচেয়ে খারাপ পরিস্থিতিতে যেমন একটি সারসংক্ষেপ প্রতিবেদন তৈরি করা যা "ব্যয়বহুল স্ট্রিং" (সম্ভবত পিডিএফ প্রতিবেদনের ফাইলের নাম ধারণ করে) ফিরে আসতে 21 মিনিট সময় নেয় এটি ফিরে আসার আগেই "মেয়াদোত্তীর্ণ" হয়ে যায়।
ওয়ান্ডবার্ড

1
@ ওয়ান্ডবার্ড গুড পয়েন্ট, আমি এটি করতে আমার উত্তর আপডেট করেছি।
স্কট চেম্বারলাইন

উত্তর:


91

এটি কোডের আমার ২ য় পুনরাবৃত্তি। কারণ MemoryCacheথ্রেড নিরাপদ আপনি প্রাথমিক পঠিত লক করার প্রয়োজন হবে না, আপনি শুধু পড়তে পারেন এবং যদি ক্যাশে আয় তারপর নাল লক চেক যদি আপনি স্ট্রিং তৈরি করা প্রয়োজন দেখতে। এটি কোডটি ব্যাপকভাবে সরল করে।

const string CacheKey = "CacheKey";
static readonly object cacheLock = new object();
private static string GetCachedData()
{

    //Returns null if the string does not exist, prevents a race condition where the cache invalidates between the contains check and the retreival.
    var cachedString = MemoryCache.Default.Get(CacheKey, null) as string;

    if (cachedString != null)
    {
        return cachedString;
    }

    lock (cacheLock)
    {
        //Check to see if anyone wrote to the cache while we where waiting our turn to write the new value.
        cachedString = MemoryCache.Default.Get(CacheKey, null) as string;

        if (cachedString != null)
        {
            return cachedString;
        }

        //The value still did not exist so we now write it in to the cache.
        var expensiveString = SomeHeavyAndExpensiveCalculation();
        CacheItemPolicy cip = new CacheItemPolicy()
                              {
                                  AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddMinutes(20))
                              };
        MemoryCache.Default.Set(CacheKey, expensiveString, cip);
        return expensiveString;
    }
}

সম্পাদনা : নীচের কোডটি অপ্রয়োজনীয় তবে মূল পদ্ধতিটি দেখানোর জন্য আমি এটি ছেড়ে যেতে চেয়েছিলাম। এটি ভবিষ্যতের দর্শনার্থীদের জন্য দরকারী হতে পারে যারা আলাদা আলাদা সংগ্রহ ব্যবহার করছেন যা থ্রেড সেফ রিড রয়েছে তবে নন-থ্রেড সেফ রাইটিং ( System.Collectionsনেমস্পেসের অধীনে প্রায় সব ক্লাসই এরকম)।

ReaderWriterLockSlimঅ্যাক্সেস রক্ষা করতে আমি এটি কীভাবে করব তা এখানে । আমরা যেখানে লক নেওয়ার অপেক্ষায় থাকাকালীন অন্য কেউ ক্যাশেড আইটেমটি তৈরি করেছে কিনা তা দেখতে আপনাকে এক ধরণের " ডাবল চেকড লকিং " করতে হবে।

const string CacheKey = "CacheKey";
static readonly ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
static string GetCachedData()
{
    //First we do a read lock to see if it already exists, this allows multiple readers at the same time.
    cacheLock.EnterReadLock();
    try
    {
        //Returns null if the string does not exist, prevents a race condition where the cache invalidates between the contains check and the retreival.
        var cachedString = MemoryCache.Default.Get(CacheKey, null) as string;

        if (cachedString != null)
        {
            return cachedString;
        }
    }
    finally
    {
        cacheLock.ExitReadLock();
    }

    //Only one UpgradeableReadLock can exist at one time, but it can co-exist with many ReadLocks
    cacheLock.EnterUpgradeableReadLock();
    try
    {
        //We need to check again to see if the string was created while we where waiting to enter the EnterUpgradeableReadLock
        var cachedString = MemoryCache.Default.Get(CacheKey, null) as string;

        if (cachedString != null)
        {
            return cachedString;
        }

        //The entry still does not exist so we need to create it and enter the write lock
        var expensiveString = SomeHeavyAndExpensiveCalculation();
        cacheLock.EnterWriteLock(); //This will block till all the Readers flush.
        try
        {
            CacheItemPolicy cip = new CacheItemPolicy()
            {
                AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddMinutes(20))
            };
            MemoryCache.Default.Set(CacheKey, expensiveString, cip);
            return expensiveString;
        }
        finally 
        {
            cacheLock.ExitWriteLock();
        }
    }
    finally
    {
        cacheLock.ExitUpgradeableReadLock();
    }
}

1
@ দার্থভেদার কীভাবে উপরের কোডটি কাজ করবে না? এছাড়াও এটি কঠোরভাবে "ডাবল চেকড লকিং" নয় তবে আমি ঠিক একই ধরণের অনুসরণ করছি এবং এটি বর্ণনা করার জন্য এটি সবচেয়ে ভাল উপায়। সে কারণেই আমি বলেছিলাম এটি এক ধরণের ডাবল চেকড লক।
স্কট চেম্বারলাইন

আমি আপনার কোড সম্পর্কে মন্তব্য না। আমি মন্তব্য করছিলাম যে ডাবল চেক লকিং কাজ করে না। আপনার কোড ঠিক আছে।
দার্থভেদার

1
এই পরিস্থিতিটি কীভাবে লক করা এবং এই ধরণের স্টোরেজটি কোন অবস্থাতেই তা বুঝতে পারে তা দেখতে আমার পক্ষে কঠিন। আপনি যদি মূল্যবোধের সমস্ত সৃষ্টিকে লক করে MemoryCacheরাখেন তবে সম্ভাব্যতার মধ্যে চলে যাওয়া those দুটি জিনিসের মধ্যে অন্তত একটি ভুল ছিল।
জন হান্না

@ স্কটচ্যাম্বারলাইন এই কোডটি স্রেফ দেখছেন, এবং লকটি অর্জন এবং ট্রাই ব্লকের মধ্যে ফেলে রাখা কোনও ব্যতিক্রমের পক্ষে সংবেদনশীল নয়। সি # ইন সংক্ষেপে লেখক এখানে এটি নিয়ে আলোচনা করেছেন, albahari.com/threading/part2.aspx#_MonitorEnter_and_MonitorExit
ব্রুটালস্পিলিটি

9
এই কোডের একটি খারাপ দিক হ'ল দু'জনকে এখনও ক্যাশে না করা হলে ক্যাশেকি "এ" ক্যাশেকে "বি" তে একটি অনুরোধ অবরুদ্ধ করবে। এটি সমাধানের জন্য আপনি একটি সমকালীন অভিধান <স্ট্রিং, অবজেক্ট> ব্যবহার করতে পারেন যেখানে আপনি ক্যাচিকে লক করার জন্য সংরক্ষণ করেন
মাইকেল

44

এখানে একটি ওপেন সোর্স লাইব্রেরি রয়েছে [দাবি অস্বীকার: আমি লিখেছি]: আইসিও দুটি লাইনের কোড দিয়ে আপনার প্রয়োজনীয়তা আবরণ করে : LazyCache

IAppCache cache = new CachingService();
var cachedResults = cache.GetOrAdd("CacheKey", 
  () => SomeHeavyAndExpensiveCalculation());

এটি ডিফল্টরূপে লকিংয়ে তৈরি করেছে সুতরাং ক্যাশেযোগ্য পদ্ধতিটি প্রতি ক্যাশে মিস একবারে কার্যকর করা হবে এবং এটি ল্যাম্বডা ব্যবহার করে যাতে আপনি একবারে "পেতে বা যুক্ত করতে" পারেন। এটি 20 মিনিটের স্লাইডিং মেয়াদে ডিফল্ট হয়।

এমনকি একটি নিউগেট প্যাকেজ রয়েছে ;)


4
ক্যাচিংয়ের ডাপার
চার্লস বার্নস

3
এটি আমাকে অলস বিকাশকারী হতে সক্ষম করে যা এটিকে সেরা উত্তর দেয়!
jdnew18

LazyCache এর জন্য গিথুব পৃষ্ঠার নিবন্ধটি উল্লেখ করার উপযুক্ত কারণ এর পিছনে কারণগুলির জন্য এটি বেশ ভাল পড়া। alastaircrabtree.com/…
রাফায়েল মের্লিন

2
এটি প্রতি কী বা প্রতি ক্যাশে লক করে?
jjxtra

1
@ ডার্কবোয়ার না এটি যেভাবে অলস এবং অলসভাবে অলসাকে ব্যবহার করা হচ্ছে তা আটকাবে না
অ্যাস্টায়ার্ট্রি

30

আমি মেমোরির ক্যাশে অ্যাডরগেটএক্সিং পদ্ধতি ব্যবহার করে এবং অলস সূচনাটি ব্যবহার করে এই সমস্যার সমাধান করেছি ।

মূলত, আমার কোডটি এরকম কিছু দেখাচ্ছে:

static string GetCachedData(string key, DateTimeOffset offset)
{
    Lazy<String> lazyObject = new Lazy<String>(() => SomeHeavyAndExpensiveCalculationThatReturnsAString());
    var returnedLazyObject = MemoryCache.Default.AddOrGetExisting(key, lazyObject, offset); 
    if (returnedLazyObject == null)
       return lazyObject.Value;
    return ((Lazy<String>) returnedLazyObject).Value;
}

এখানে সবচেয়ে খারাপ ক্ষেত্রে দৃশ্যমানটি হ'ল আপনি একই Lazyজিনিসটি দু'বার তৈরি করেছেন। তবে এটি বেশ তুচ্ছ। AddOrGetExistingগ্যারান্টির ব্যবহার যে আপনি কেবলমাত্র একবারের জন্য একটি উদাহরণ পাবেন Lazyএবং তাই আপনাকে একবার ব্যয়বহুল আরম্ভ করার পদ্ধতিটি কল করার নিশ্চয়তাও দেওয়া হচ্ছে।


4
এই ধরণের পদ্ধতির সমস্যা হ'ল আপনি অবৈধ ডেটা sertোকাতে পারেন। যদি SomeHeavyAndExpensiveCalculationThatResultsAString()কোনও ব্যতিক্রম ছুঁড়ে ফেলা হয়, তবে এটি ক্যাশে আটকে আছে। এমনকি ক্ষণস্থায়ী ব্যতিক্রমগুলি এর সাথে ক্যাশেড হবে Lazy<T>: এমএসডিএন.মাইক্রোসফট.ইন.উস
স্কট ওয়েগনার

2
যদিও এটি সত্য যে অলস <T> আরম্ভের ব্যতিক্রম ব্যর্থ হলে কোনও ত্রুটি ফিরে আসতে পারে, এটি সনাক্ত করা খুব সহজ বিষয়। এরপরে আপনি ক্যাশি থেকে কোনও ত্রুটির সমাধান করে এমন কোনও অলস <T> উচ্ছেদ করতে পারেন, একটি নতুন অলস <টি> তৈরি করুন, এটি ক্যাশে রাখুন এবং সমাধান করুন। আমাদের নিজস্ব কোডে, আমরা অনুরূপ কিছু করি। আমরা ত্রুটি ছুঁড়ে ফেলার আগে আমরা একটি সেট সংখ্যার পুনরায় চেষ্টা করি ry
কিথ

12
অ্যাডওরগেটএক্স্টিং রিটার্ন নালুন যদি আইটেমটি উপস্থিত না থাকে, সুতরাং আপনার সেই ক্ষেত্রে অলস অধ্যায়টি চেক করে ফিরে আসা উচিত
জিয়ান মার্কো

1
ল্যাজিথ্রেডস্যাফটিমোড.পাবলিকেশন কেবলমাত্র ব্যতিক্রমগুলির ক্যাচিং এড়ানো হবে।
ক্লেমেন্ট

2
এই ব্লগ পোস্টে দেওয়া মন্তব্য অনুসারে যদি ক্যাশে প্রবেশ শুরু করা অত্যন্ত ব্যয়বহুল হয় তবে পাবলিকেশনঅনলি ব্যবহার না করে কেবল একটি ব্যতিক্রম (কেবলমাত্র ব্লগ পোস্টে উদাহরণ হিসাবে দেখানো হয়েছে) উচ্ছেদ করা ভাল, কারণ সম্ভাবনা রয়েছে যে সমস্ত থ্রেডগুলি একই সাথে প্রারম্ভককে কল করতে পারে।
বিসিআর

15

আমি ধরে নিই এই কোডটিতে সামঞ্জস্যপূর্ণ সমস্যা রয়েছে:

প্রকৃতপক্ষে, এটি সম্ভবত বেশ উন্নত, যদিও একটি সম্ভাব্য উন্নতি সহ।

এখন, সাধারণভাবে যেখানে আমাদের একাধিক থ্রেড রয়েছে প্রথম ব্যবহারে একটি ভাগ করা মান নির্ধারণ করে, প্রাপ্ত মানটি সেট করা এবং সেট করা লক না করে তা হতে পারে:

  1. বিপর্যয়কর - অন্যান্য কোড কেবলমাত্র একটি উদাহরণ উপস্থিত রয়েছে বলে ধরে নেবে।
  2. বিপর্যয়কর - কোডটি যা দৃষ্টান্তটি গ্রহণ করে তা কেবলমাত্র একটি (বা সম্ভবত একটি নির্দিষ্ট সংখ্যক) সমবর্তী ক্রিয়াকলাপ সহ্য করতে পারে না।
  3. বিপর্যয়কর - স্টোরেজ করার উপায়গুলি থ্রেড-নিরাপদ নয় (উদাহরণস্বরূপ একটি অভিধানে দুটি থ্রেড যুক্ত রয়েছে এবং আপনি সমস্ত ধরণের বাজে ত্রুটি পেতে পারেন)।
  4. সাব-অনুকূল - সামগ্রিক কর্মক্ষমতা খারাপ হয় যদি লকিং নিশ্চিত করা যে কেবল একটি থ্রেড মান অর্জনের কাজ করেছে।
  5. অনুকূল - একাধিক থ্রেড থাকার ব্যয় অপ্রয়োজনীয় কাজ প্রতিরোধের ব্যয়ের চেয়ে কম, বিশেষত যেহেতু এটি কেবল অপেক্ষাকৃত সংক্ষিপ্ত সময়ের মধ্যেই ঘটতে পারে।

তবে এখানে বিবেচনা করা যা MemoryCacheএন্ট্রিগুলি উড়িয়ে দিতে পারে:

  1. যদি একাধিক উদাহরণ থাকা দুর্যোগজনক MemoryCacheহয় তবে তা ভুল পদ্ধতির।
  2. যদি আপনাকে অবশ্যই একযোগে সৃষ্টি প্রতিরোধ করতে হয় তবে আপনার এটি তৈরির পর্যায়ে করা উচিত।
  3. MemoryCache object বস্তুর অ্যাক্সেসের ক্ষেত্রে থ্রেড-নিরাপদ, তাই এটি এখানে উদ্বেগের বিষয় নয়।

এই দুটি সম্ভাবনার অবশ্যই অবশ্যই চিন্তা করতে হবে, যদিও একই স্ট্রিংয়ের দুটি উদাহরণ বিদ্যমান থাকলেই যদি সমস্যা হয় তবে আপনি যদি এখানে বিশেষ প্রয়োগ করেন না এমন খুব নির্দিষ্ট অপটিমেশান * করছেন *

সুতরাং, আমরা সম্ভাবনাগুলি রেখেছি:

  1. এটি বর্তমানে এমন ডুপ্লিকেট কলের খরচ এড়ানোর জন্য সস্তা SomeHeavyAndExpensiveCalculation()
  2. এটি বর্তমানে এমন ডুপ্লিকেট কলের খরচ এড়াতে না সস্তা SomeHeavyAndExpensiveCalculation()

এবং এটি কাজ করা কঠিন হতে পারে (প্রকৃতপক্ষে, যেখানে আপনি এটি কাজ করতে পারবেন তা ধরে নেওয়ার চেয়ে এটি যেভাবে প্রোফাইলিংয়ের পক্ষে উপযুক্ত তা এই ধরণের)। এখানে বিবেচনা করার মতো বিষয় যদিও সন্নিবেশকে লক করার সর্বাধিক সুস্পষ্ট উপায়গুলি সম্পর্কিত নয় এমনগুলি সহ ক্যাশে সমস্ত সংযোজন প্রতিরোধ করবে ।

এর অর্থ হ'ল যদি আমাদের 50 টি থ্রেডগুলি 50 টি বিভিন্ন মান নির্ধারণের চেষ্টা করে থাকে তবে আমাদের 50 টি থ্রেড একে অপরের জন্য অপেক্ষা করতে হবে, যদিও তারা একই গণনা করতে যাচ্ছিল না।

সেই হিসাবে, জাতি সম্ভবত শর্ত এড়ায় না এমন কোডের চেয়ে আপনি সম্ভবত আপনার কোডটি ভাল করে রেখেছেন এবং যদি রেস-শর্তটি কোনও সমস্যা হয়ে থাকে তবে আপনাকে সম্ভবত অন্য কোথাও এটি পরিচালনা করতে হবে, বা অন্যরকম প্রয়োজন পুরানো এন্ট্রিগুলি বহিষ্কার করে এমন একের চেয়ে ক্যাশিং কৌশল †

আমি যে জিনিসটি পরিবর্তন করব তা হ'ল আমি কলটির Set()সাথে প্রতিস্থাপন করব AddOrGetExisting()। উপরের দিক থেকে এটি স্পষ্ট হওয়া উচিত যে এটি সম্ভবত প্রয়োজনীয় নয়, তবে এটি সদ্য প্রাপ্ত আইটেমটি সংগ্রহের অনুমতি দেয়, সামগ্রিক স্মৃতি ব্যবহার হ্রাস করে এবং কম প্রজন্মের উচ্চতর অনুপাতকে উচ্চ প্রজন্মের সংগ্রহের অনুমতি দেয়।

হ্যাঁ, আপনি সম্মিলন রোধ করতে ডাবল-লকিং ব্যবহার করতে পারেন, তবে হয় সম্মতিটি আসলে কোনও সমস্যা নয়, বা আপনার মানগুলি ভুল উপায়ে সংরক্ষণ করা, বা দোকানে ডাবল লক করা সমাধানের পক্ষে সেরা উপায় নয় would ।

* যদি আপনি জানেন যে কেবল একটি স্ট্রিংয়ের প্রতিটি সেট বিদ্যমান রয়েছে তবে আপনি সমতা তুলনাটি অপ্টিমাইজ করতে পারবেন, যা কেবলমাত্র দুটি সময়ের সাথে একটি স্ট্রিংয়ের দুটি কপি থাকা কেবলমাত্র উপ-অনুকূলের চেয়ে ভুল হতে পারে তবে আপনি করতে চান বোঝার জন্য এটির জন্য বিভিন্ন ধরণের ক্যাশে। যেমন সাজানো XmlReaderঅভ্যন্তরীণভাবে করে।

Likely বেশিরভাগই হয় অনির্দিষ্টকালের জন্য সঞ্চয় করে রাখা দুর্বল, অথবা দুর্বল উল্লেখগুলি ব্যবহার করে যাতে কোনও বিদ্যমান ব্যবহার না থাকলে কেবলমাত্র এন্ট্রিগুলি বহিষ্কার করবে।


1

গ্লোবাল লক এড়ানোর জন্য, আপনি মেমরির ব্যবহার বিস্ফোরিত না করে, প্রতি কী অনুসারে একটি লক প্রয়োগ করতে সিঙ্গেলটনক্যাচ ব্যবহার করতে পারেন (লক অবজেক্টগুলি আর রেফারেন্স করা না হয়ে মুছে ফেলা হয়, এবং অর্জন / রিলিজ থ্রেড নিরাপদ গ্যারান্টিযুক্ত যে কেবল 1 টি উদাহরণ ব্যবহারের তুলনায় কখনও ব্যবহৃত হয়) এবং অদলবদল)।

এটি ব্যবহার করে দেখতে এটির মতো দেখাচ্ছে:

SingletonCache<string, object> keyLocks = new SingletonCache<string, object>();

const string CacheKey = "CacheKey";
static string GetCachedData()
{
    string expensiveString =null;
    if (MemoryCache.Default.Contains(CacheKey))
    {
        return MemoryCache.Default[CacheKey] as string;
    }

    // double checked lock
    using (var lifetime = keyLocks.Acquire(url))
    {
        lock (lifetime.Value)
        {
           if (MemoryCache.Default.Contains(CacheKey))
           {
              return MemoryCache.Default[CacheKey] as string;
           }

           cacheItemPolicy cip = new CacheItemPolicy()
           {
              AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddMinutes(20))
           };
           expensiveString = SomeHeavyAndExpensiveCalculation();
           MemoryCache.Default.Set(CacheKey, expensiveString, cip);
           return expensiveString;
        }
    }      
}

কোডটি এখানে গিটহাবে রয়েছে: https://github.com / বিটফাস্টার / বিটফাস্টার C ক্যাচিং

Install-Package BitFaster.Caching

এছাড়াও একটি এলআরইউ বাস্তবায়ন রয়েছে যা মেমোরি ক্যাশের তুলনায় হালকা ওজন এবং এর বেশ কয়েকটি সুবিধা রয়েছে - দ্রুত সমবর্তী পড়া এবং লেখার জন্য, সীমানা আকারের, কোনও ব্যাকগ্রাউন্ড থ্রেড, কোনও অভ্যন্তরীণ পারফ কাউন্টার ইত্যাদি (দাবি অস্বীকার, আমি এটি লিখেছি)।


0

কনসোল উদাহরণ কয়েক MemoryCache , "কেমন করে সংরক্ষণ করতে / সহজ বর্গ বস্তু পেতে"

চালু এবং টিপে পর আউটপুট Any keyছাড়া Esc:

ক্যাশে সংরক্ষণ করছেন!
ক্যাশে থেকে পাওয়া!
কিছু 1
কেউ 2

    class Some
    {
        public String text { get; set; }

        public Some(String text)
        {
            this.text = text;
        }

        public override string ToString()
        {
            return text;
        }
    }

    public static MemoryCache cache = new MemoryCache("cache");

    public static string cache_name = "mycache";

    static void Main(string[] args)
    {

        Some some1 = new Some("some1");
        Some some2 = new Some("some2");

        List<Some> list = new List<Some>();
        list.Add(some1);
        list.Add(some2);

        do {

            if (cache.Contains(cache_name))
            {
                Console.WriteLine("Getting from cache!");
                List<Some> list_c = cache.Get(cache_name) as List<Some>;
                foreach (Some s in list_c) Console.WriteLine(s);
            }
            else
            {
                Console.WriteLine("Saving to cache!");
                cache.Set(cache_name, list, DateTime.Now.AddMinutes(10));                   
            }

        } while (Console.ReadKey(true).Key != ConsoleKey.Escape);

    }

0
public interface ILazyCacheProvider : IAppCache
{
    /// <summary>
    /// Get data loaded - after allways throw cached result (even when data is older then needed) but very fast!
    /// </summary>
    /// <param name="key"></param>
    /// <param name="getData"></param>
    /// <param name="slidingExpiration"></param>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    T GetOrAddPermanent<T>(string key, Func<T> getData, TimeSpan slidingExpiration);
}

/// <summary>
/// Initialize LazyCache in runtime
/// </summary>
public class LazzyCacheProvider: CachingService, ILazyCacheProvider
{
    private readonly Logger _logger = LogManager.GetLogger("MemCashe");
    private readonly Hashtable _hash = new Hashtable();
    private readonly List<string>  _reloader = new List<string>();
    private readonly ConcurrentDictionary<string, DateTime> _lastLoad = new ConcurrentDictionary<string, DateTime>();  


    T ILazyCacheProvider.GetOrAddPermanent<T>(string dataKey, Func<T> getData, TimeSpan slidingExpiration)
    {
        var currentPrincipal = Thread.CurrentPrincipal;
        if (!ObjectCache.Contains(dataKey) && !_hash.Contains(dataKey))
        {
            _hash[dataKey] = null;
            _logger.Debug($"{dataKey} - first start");
            _lastLoad[dataKey] = DateTime.Now;
            _hash[dataKey] = ((object)GetOrAdd(dataKey, getData, slidingExpiration)).CloneObject();
            _lastLoad[dataKey] = DateTime.Now;
           _logger.Debug($"{dataKey} - first");
        }
        else
        {
            if ((!ObjectCache.Contains(dataKey) || _lastLoad[dataKey].AddMinutes(slidingExpiration.Minutes) < DateTime.Now) && _hash[dataKey] != null)
                Task.Run(() =>
                {
                    if (_reloader.Contains(dataKey)) return;
                    lock (_reloader)
                    {
                        if (ObjectCache.Contains(dataKey))
                        {
                            if(_lastLoad[dataKey].AddMinutes(slidingExpiration.Minutes) > DateTime.Now)
                                return;
                            _lastLoad[dataKey] = DateTime.Now;
                            Remove(dataKey);
                        }
                        _reloader.Add(dataKey);
                        Thread.CurrentPrincipal = currentPrincipal;
                        _logger.Debug($"{dataKey} - reload start");
                        _hash[dataKey] = ((object)GetOrAdd(dataKey, getData, slidingExpiration)).CloneObject();
                        _logger.Debug($"{dataKey} - reload");
                        _reloader.Remove(dataKey);
                    }
                });
        }
        if (_hash[dataKey] != null) return (T) (_hash[dataKey]);

        _logger.Debug($"{dataKey} - dummy start");
        var data = GetOrAdd(dataKey, getData, slidingExpiration);
        _logger.Debug($"{dataKey} - dummy");
        return (T)((object)data).CloneObject();
    }
}

খুব দ্রুত LazyCache :) আমি REST এপিআই সংগ্রহস্থলের জন্য এই কোডটি লিখেছি।
art24war

0

কিছুটা দেরি হলেও ... সম্পূর্ণ বাস্তবায়ন:

    [HttpGet]
    public async Task<HttpResponseMessage> GetPageFromUriOrBody(RequestQuery requestQuery)
    {
        log(nameof(GetPageFromUriOrBody), nameof(requestQuery));
        var responseResult = await _requestQueryCache.GetOrCreate(
            nameof(GetPageFromUriOrBody)
            , requestQuery
            , (x) => getPageContent(x).Result);
        return Request.CreateResponse(System.Net.HttpStatusCode.Accepted, responseResult);
    }
    static MemoryCacheWithPolicy<RequestQuery, string> _requestQueryCache = new MemoryCacheWithPolicy<RequestQuery, string>();

getPageContentস্বাক্ষরটি এখানে :

async Task<string> getPageContent(RequestQuery requestQuery);

এবং MemoryCacheWithPolicyবাস্তবায়ন এখানে :

public class MemoryCacheWithPolicy<TParameter, TResult>
{
    static ILogger _nlogger = new AppLogger().Logger;
    private MemoryCache _cache = new MemoryCache(new MemoryCacheOptions() 
    {
        //Size limit amount: this is actually a memory size limit value!
        SizeLimit = 1024 
    });

    /// <summary>
    /// Gets or creates a new memory cache record for a main data
    /// along with parameter data that is assocciated with main main.
    /// </summary>
    /// <param name="key">Main data cache memory key.</param>
    /// <param name="param">Parameter model that assocciated to main model (request result).</param>
    /// <param name="createCacheData">A delegate to create a new main data to cache.</param>
    /// <returns></returns>
    public async Task<TResult> GetOrCreate(object key, TParameter param, Func<TParameter, TResult> createCacheData)
    {
        // this key is used for param cache memory.
        var paramKey = key + nameof(param);

        if (!_cache.TryGetValue(key, out TResult cacheEntry))
        {
            // key is not in the cache, create data through the delegate.
            cacheEntry = createCacheData(param);
            createMemoryCache(key, cacheEntry, paramKey, param);

            _nlogger.Warn(" cache is created.");
        }
        else
        {
            // data is chached so far..., check if param model is same (or changed)?
            if(!_cache.TryGetValue(paramKey, out TParameter cacheParam))
            {
                //exception: this case should not happened!
            }

            if (!cacheParam.Equals(param))
            {
                // request param is changed, create data through the delegate.
                cacheEntry = createCacheData(param);
                createMemoryCache(key, cacheEntry, paramKey, param);
                _nlogger.Warn(" cache is re-created (param model has been changed).");
            }
            else
            {
                _nlogger.Trace(" cache is used.");
            }

        }
        return await Task.FromResult<TResult>(cacheEntry);
    }
    MemoryCacheEntryOptions createMemoryCacheEntryOptions(TimeSpan slidingOffset, TimeSpan relativeOffset)
    {
        // Cache data within [slidingOffset] seconds, 
        // request new result after [relativeOffset] seconds.
        return new MemoryCacheEntryOptions()

            // Size amount: this is actually an entry count per 
            // key limit value! not an actual memory size value!
            .SetSize(1)

            // Priority on removing when reaching size limit (memory pressure)
            .SetPriority(CacheItemPriority.High)

            // Keep in cache for this amount of time, reset it if accessed.
            .SetSlidingExpiration(slidingOffset)

            // Remove from cache after this time, regardless of sliding expiration
            .SetAbsoluteExpiration(relativeOffset);
        //
    }
    void createMemoryCache(object key, TResult cacheEntry, object paramKey, TParameter param)
    {
        // Cache data within 2 seconds, 
        // request new result after 5 seconds.
        var cacheEntryOptions = createMemoryCacheEntryOptions(
            TimeSpan.FromSeconds(2)
            , TimeSpan.FromSeconds(5));

        // Save data in cache.
        _cache.Set(key, cacheEntry, cacheEntryOptions);

        // Save param in cache.
        _cache.Set(paramKey, param, cacheEntryOptions);
    }
    void checkCacheEntry<T>(object key, string name)
    {
        _cache.TryGetValue(key, out T value);
        _nlogger.Fatal("Key: {0}, Name: {1}, Value: {2}", key, name, value);
    }
}

nloggerআচরণের nLogসন্ধানে কেবল আপত্তি MemoryCacheWithPolicy। আমি যদি মেমোরি ক্যাশেটি পুনরায় তৈরি করি তবে যদি অনুরোধের অবজেক্টটি ( RequestQuery requestQuery) প্রতিনিধি ( Func<TParameter, TResult> createCacheData) এর মাধ্যমে পরিবর্তিত হয় বা স্লাইডিং বা পরম সময় তাদের সীমাতে পৌঁছে যায় তখন পুনরায় তৈরি করি। মনে রাখবেন যে সবকিছুই অ্যাসিঙ্ক;)


হতে পারে আপনার উত্তর এই প্রশ্নের সাথে আরও জড়িত: অ্যাসিঙ্ক থ্রেডসেফ মেমোরি ক্যাশে থেকে পান
থিওডর জৌলিয়াস

আমার ধারণা তাই, তবে এখনও কার্যকর অভিজ্ঞতা বিনিময়;)
স্যাম সায়ারিয়ান

0

কোনটি ভাল তা চয়ন করা কঠিন; লক বা রিডার রাইটারলকস্লিম। আপনার পড়া এবং লেখার সংখ্যা এবং অনুপাত ইত্যাদির সত্যিকারের বিশ্ব পরিসংখ্যান দরকার

তবে আপনি যদি বিশ্বাস করেন যে "লক" ব্যবহার করা সঠিক উপায়। তারপরে এখানে বিভিন্ন প্রয়োজনের জন্য আলাদা সমাধান রয়েছে। আমি কোডে অ্যালান জু এর সমাধানও অন্তর্ভুক্ত করি। কারণ উভয়ই বিভিন্ন প্রয়োজনের জন্য প্রয়োজন হতে পারে।

এখানে প্রয়োজনীয়তা রয়েছে, আমাকে এই সমাধানে নিয়ে যাচ্ছেন:

  1. আপনি কোনও কারণে 'গেটডেটা' ফাংশনটি সরবরাহ করতে বা করতে পারবেন না। সম্ভবত 'গেটডাটা' ফাংশনটি ভারী কনস্ট্রাক্টরের সাথে অন্য কোনও শ্রেণিতে অবস্থিত এবং আপনি অনিবার্য এটি নিশ্চিত না হওয়া পর্যন্ত আপনি কোনও উদাহরণ তৈরি করতে চান না।
  2. আপনার অ্যাপ্লিকেশনটির বিভিন্ন অবস্থান / স্তরগুলি থেকে একই ক্যাশেড ডেটা অ্যাক্সেস করতে হবে। এবং এই বিভিন্ন অবস্থানে একই লকার অবজেক্টে অ্যাক্সেস নেই।
  3. আপনার কাছে ধ্রুবক ক্যাশে কী নেই। উদাহরণ স্বরূপ; সেশনআইড ক্যাশে কী দিয়ে কিছু ডেটা ক্যাশে করা দরকার।

কোড:

using System;
using System.Runtime.Caching;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace CachePoc
{
    class Program
    {
        static object everoneUseThisLockObject4CacheXYZ = new object();
        const string CacheXYZ = "CacheXYZ";
        static object everoneUseThisLockObject4CacheABC = new object();
        const string CacheABC = "CacheABC";

        static void Main(string[] args)
        {
            //Allan Xu's usage
            string xyzData = MemoryCacheHelper.GetCachedDataOrAdd<string>(CacheXYZ, everoneUseThisLockObject4CacheXYZ, 20, SomeHeavyAndExpensiveXYZCalculation);
            string abcData = MemoryCacheHelper.GetCachedDataOrAdd<string>(CacheABC, everoneUseThisLockObject4CacheXYZ, 20, SomeHeavyAndExpensiveXYZCalculation);

            //My usage
            string sessionId = System.Web.HttpContext.Current.Session["CurrentUser.SessionId"].ToString();
            string yvz = MemoryCacheHelper.GetCachedData<string>(sessionId);
            if (string.IsNullOrWhiteSpace(yvz))
            {
                object locker = MemoryCacheHelper.GetLocker(sessionId);
                lock (locker)
                {
                    yvz = MemoryCacheHelper.GetCachedData<string>(sessionId);
                    if (string.IsNullOrWhiteSpace(yvz))
                    {
                        DatabaseRepositoryWithHeavyConstructorOverHead dbRepo = new DatabaseRepositoryWithHeavyConstructorOverHead();
                        yvz = dbRepo.GetDataExpensiveDataForSession(sessionId);
                        MemoryCacheHelper.AddDataToCache(sessionId, yvz, 5);
                    }
                }
            }
        }


        private static string SomeHeavyAndExpensiveXYZCalculation() { return "Expensive"; }
        private static string SomeHeavyAndExpensiveABCCalculation() { return "Expensive"; }

        public static class MemoryCacheHelper
        {
            //Allan Xu's solution
            public static T GetCachedDataOrAdd<T>(string cacheKey, object cacheLock, int minutesToExpire, Func<T> GetData) where T : class
            {
                //Returns null if the string does not exist, prevents a race condition where the cache invalidates between the contains check and the retreival.
                T cachedData = MemoryCache.Default.Get(cacheKey, null) as T;

                if (cachedData != null)
                    return cachedData;

                lock (cacheLock)
                {
                    //Check to see if anyone wrote to the cache while we where waiting our turn to write the new value.
                    cachedData = MemoryCache.Default.Get(cacheKey, null) as T;

                    if (cachedData != null)
                        return cachedData;

                    cachedData = GetData();
                    MemoryCache.Default.Set(cacheKey, cachedData, DateTime.Now.AddMinutes(minutesToExpire));
                    return cachedData;
                }
            }

            #region "My Solution"

            readonly static ConcurrentDictionary<string, object> Lockers = new ConcurrentDictionary<string, object>();
            public static object GetLocker(string cacheKey)
            {
                CleanupLockers();

                return Lockers.GetOrAdd(cacheKey, item => (cacheKey, new object()));
            }

            public static T GetCachedData<T>(string cacheKey) where T : class
            {
                CleanupLockers();

                T cachedData = MemoryCache.Default.Get(cacheKey) as T;
                return cachedData;
            }

            public static void AddDataToCache(string cacheKey, object value, int cacheTimePolicyMinutes)
            {
                CleanupLockers();

                MemoryCache.Default.Add(cacheKey, value, DateTimeOffset.Now.AddMinutes(cacheTimePolicyMinutes));
            }

            static DateTimeOffset lastCleanUpTime = DateTimeOffset.MinValue;
            static void CleanupLockers()
            {
                if (DateTimeOffset.Now.Subtract(lastCleanUpTime).TotalMinutes > 1)
                {
                    lock (Lockers)//maybe a better locker is needed?
                    {
                        try//bypass exceptions
                        {
                            List<string> lockersToRemove = new List<string>();
                            foreach (var locker in Lockers)
                            {
                                if (!MemoryCache.Default.Contains(locker.Key))
                                    lockersToRemove.Add(locker.Key);
                            }

                            object dummy;
                            foreach (string lockerKey in lockersToRemove)
                                Lockers.TryRemove(lockerKey, out dummy);

                            lastCleanUpTime = DateTimeOffset.Now;
                        }
                        catch (Exception)
                        { }
                    }
                }

            }
            #endregion
        }
    }

    class DatabaseRepositoryWithHeavyConstructorOverHead
    {
        internal string GetDataExpensiveDataForSession(string sessionId)
        {
            return "Expensive data from database";
        }
    }

}
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.