国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計 > 正文

那些年我們一起追過的緩存寫法(一)

2019-11-17 02:39:48
字體:
供稿:網(wǎng)友

那些年我們一起追過的緩存寫法(一)

2015-01-04 08:36 by 蘑菇先生, ... 閱讀, ... 評論, 收藏, 編輯

本篇主要介紹下樓主平常項目中,緩存使用經(jīng)驗和遇到過的問題。

閱讀目錄:

  1. 基本寫法
  2. 緩存雪崩
  3. 全局鎖,實例鎖
  4. 字符串鎖
  5. 緩存穿透
  6. 再談緩存雪崩
  7. 總結(jié)

基本寫法

為了方便演示,這里使用Runtime.Cache做緩存容器,并定義個簡單操作類。如下:

 public class CacheHelper    {        public static object Get(string cacheKey)        {            return HttPRuntime.Cache[cacheKey];        }        public static void Add(string cacheKey, object obj, int cacheMinute)        {            HttpRuntime.Cache.Insert(cacheKey, obj, null, DateTime.Now.AddMinutes(cacheMinute),                Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);        }    }

簡單讀取:

    public object GetMemberSigninDays1()        {            const int cacheTime = 5;            const string cacheKey = "mushroomsir";            var cacheValue = CacheHelper.Get(cacheKey);            if (cacheValue != null)                return cacheValue;            cacheValue = "395"; //這里一般是 sql查詢數(shù)據(jù)。 例:395 簽到天數(shù)            CacheHelper.Add(cacheKey, cacheValue, cacheTime);            return cacheValue;        }    

在項目中,有不少這樣寫法,這樣寫并沒有錯,但在并發(fā)量上來后就容易出問題。

緩存雪崩

緩存雪崩是由于緩存失效(過期),新緩存未到期間。

這個中間時間內(nèi),所有請求都去查詢數(shù)據(jù)庫,而對數(shù)據(jù)庫CPU和內(nèi)存造成巨大壓力,前端連接數(shù)不夠、查詢阻塞。

這個中間時間并沒有那么短,比如sql查詢1秒,加上傳輸解析0.5秒。 就是說1.5秒內(nèi)所有用戶查詢,都是直接查詢數(shù)據(jù)庫的。

碰到這種情況,使用最多的解決方案就是加鎖排隊。

全局鎖,實例鎖

  public static object obj1 = new object();        public object GetMemberSigninDays2()        {            const int cacheTime = 5;            const string cacheKey = "mushroomsir";            var cacheValue = CacheHelper.Get(cacheKey);            if (cacheValue != null)                return cacheValue;            //lock (obj1)         //全局鎖            //{            //    cacheValue = CacheHelper.Get(cacheKey);            //    if (cacheValue != null)            //        return cacheValue;            //    cacheValue = "395"; //這里一般是 sql查詢數(shù)據(jù)。 例:395 簽到天數(shù)            //    CacheHelper.Add(cacheKey, cacheValue, cacheTime);            //}            lock (this)            {                cacheValue = CacheHelper.Get(cacheKey);                if (cacheValue != null)                    return cacheValue;                cacheValue = "395"; //這里一般是 sql查詢數(shù)據(jù)。 例:395 簽到天數(shù)                CacheHelper.Add(cacheKey, cacheValue, cacheTime);            }            return cacheValue;        }

第一種:lock (obj1) 是全局鎖可以滿足,但要為每個函數(shù)都聲明一個obj,不然在A、B函數(shù)都鎖obj1時,必然會讓其中一個阻塞。

第二種:lock (this) 這個鎖當(dāng)前實例,對其他實例無效,那這個鎖就沒什么效果了,當(dāng)然使用單例模式的對象可以鎖。

在當(dāng)前實例中:A函數(shù)鎖當(dāng)前實例,其他也鎖當(dāng)前實例的函數(shù)的讀寫,也被阻塞,這種做法也不可取。

字符串鎖

既然鎖對象不行,利用字符串的特性,直接鎖緩存的key呢

    public object GetMemberSigninDays3()        {            const int cacheTime = 5;            const string cacheKey = "mushroomsir";            var cacheValue = CacheHelper.Get(cacheKey);            if (cacheValue != null)                return cacheValue;            const string lockKey = cacheKey + "n(*≧▽≦*)n";            //lock (cacheKey)            //{            //    cacheValue = CacheHelper.Get(cacheKey);            //    if (cacheValue != null)            //        return cacheValue;            //    cacheValue = "395"; //這里一般是 sql查詢數(shù)據(jù)。 例:395 簽到天數(shù)            //    CacheHelper.Add(cacheKey, cacheValue, cacheTime);            //}            lock (lockKey)            {                cacheValue = CacheHelper.Get(cacheKey);                if (cacheValue != null)                    return cacheValue;                cacheValue = "395"; //這里一般是 sql查詢數(shù)據(jù)。 例:395 簽到天數(shù)                CacheHelper.Add(cacheKey, cacheValue, cacheTime);            }            return cacheValue;        }

第一種:lock (cacheName) 有問題,因為字符串也是共享的,會阻塞其他使用這個字符串的操作行為。

具體請參考之前的博文 c#語言-多線程中的鎖系統(tǒng)(一)。

因為字符串被公共語言運(yùn)行庫 (CLR)暫留,這意味著整個程序中任何給定字符串都只有一個實例,所以才會用下面第二種方法。

第二種:lock (lockKey) 可以滿足。其目的就是為了保證鎖的粒度最小并且全局唯一性,只鎖當(dāng)前緩存的查詢行為。

緩存穿透

先舉個簡單例子:一般網(wǎng)站經(jīng)常會緩存用戶搜索的結(jié)果,如果數(shù)據(jù)庫查詢不到,是不會做緩存的。但如果頻繁查這個空關(guān)鍵字,會導(dǎo)致每次請求都直接查詢數(shù)據(jù)庫了。

例子就是緩存穿透,請求繞過緩存直接查數(shù)據(jù)庫,這也是經(jīng)常提的緩存命中率問題。

  public object GetMemberSigninDays4()        {            const int cacheTime = 5;            const string cacheKey = "mushroomsir";            var cacheValue = CacheHelper.Get(cacheKey);            if (cacheValue != null)                return cacheValue;            const string lockKey = cacheKey + "n(*≧▽≦*)n";            lock (lockKey)            {                cacheValue = CacheHelper.Get(cacheKey);                if (cacheValue != null)                    return cacheValue;                cacheValue = null; //數(shù)據(jù)庫查詢不到,為空。                //if (cacheValue2 == null)                //{                //    return null;  //一般為空,不做緩存                //}                if (cacheValue == null)                {                    cacheValue = string.Empty; //如果發(fā)現(xiàn)為空,我設(shè)置個默認(rèn)值,也緩存起來。                }                CacheHelper.Add(cacheKey, cacheValue, cacheTime);            }            return cacheValue;        }

如果把查詢不到的空結(jié)果,也給緩存起來,這樣下次同樣的請求就可以直接返回null了,即可以避免當(dāng)查詢的值為空時引起的緩存穿透。

可以單獨設(shè)置個緩存區(qū)域存儲空值,對要查詢的key進(jìn)行預(yù)先校驗,然后再放行給后面的正常緩存處理邏輯。

再談緩存雪崩

前面不是用加鎖排隊方式就解決了嗎?其實加鎖排隊只是為了減輕數(shù)據(jù)庫的壓力,本質(zhì)上并沒有提高系統(tǒng)吞吐量。

假設(shè)在高并發(fā)下,緩存重建期間key是鎖著的,這是過來1000個請求999個都在阻塞的。導(dǎo)致的結(jié)果是用戶等待超時,這是非常不優(yōu)化的體驗。

這種行為本質(zhì)上是把多線程的Web服務(wù)器,在此時給變成單線程處理了,會導(dǎo)致大量的阻塞。對于系統(tǒng)資源也是一種浪費(fèi),因緩存重建而阻塞的線程本可以處理更多請求的。

這里提出一種解決方案是:

  public object GetMemberSigninDays5()        {            const int cacheTime = 5;            const string cacheKey = "mushroomsir";            //緩存標(biāo)記。            const string cacheSign = cacheKey + "_Sign";            var sign = CacheHelper.Get(cacheSign);            //獲取緩存值            var cacheValue = CacheHelper.Get(cacheKey);            if (sign != null)                return cacheValue; //未過期,直接返回。            lock (cacheSign)            {                sign = CacheHelper.Get(cacheSign);                if (sign != null)                    return cacheValue;                CacheHelper.Add(cacheSign, "1", cacheTime);                ThreadPool.QueueUserWorkItem((arg) =>                {                    cacheValue = "395"; //這里一般是 sql查詢數(shù)據(jù)。 例:395 簽到天數(shù)                    CacheHelper.Add(cacheKey, cacheValue, cacheTime*2); //日期設(shè)緩存時間的2倍,用于臟讀。                });            }            return cacheValue;        }

從代碼中看出,我們多使用了一個緩存標(biāo)記key,并使用雙檢鎖校驗保證后面邏輯不會多次執(zhí)行。

緩存標(biāo)記key: 緩存標(biāo)記key只是一個記錄實際key過期時間的標(biāo)記,它的緩存值可以是任意值,比如1。 它主要用來在實際key過期后,觸發(fā)通知另外的線程在后臺去更新實際key的緩存。

實際key: 它的過期時間會延長1倍,例:本來5分鐘,現(xiàn)在設(shè)置為10分鐘。 這樣做的目的是,當(dāng)緩存標(biāo)記key過期后,實際緩存還能以臟數(shù)據(jù)返回給調(diào)用端,直到另外的線程在后臺更新完成后,才會返回新緩存。

關(guān)于實際key的過期時間延長1倍,還是2、3倍都是可以的。只要大于正常緩存過期時間,并且能保證在延長的時間內(nèi)足夠拉取數(shù)據(jù)即可。

還一個好處就是,如果突然db掛了,臟數(shù)據(jù)的存在可以保證前端系統(tǒng)不會拿不到數(shù)據(jù)。

這樣做后,就可以一定程度上提高系統(tǒng)吞吐量。

總結(jié)

文中說的阻塞其他函數(shù)指的是,并發(fā)情況下鎖同一對象,比如一個函數(shù)鎖A對象,另外的函數(shù)就必須等待A對象的鎖釋放后才能再次進(jìn)鎖。

關(guān)于更新緩存,可以單開一個線程去專門跑緩存更新,圖方便的話扔線程池里面即可。

實際項目中,緩存層框架的封裝往往要復(fù)雜的多,如果并發(fā)量比較小,這樣寫反而會增加代碼的復(fù)雜度,具體要根據(jù)實際情況來取舍。

系列目錄:

那些年我們一起追過的緩存寫法(一)

那些年我們一起追過的緩存寫法(二)

那些年我們一起追過的緩存寫法(三)


發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 黄大仙区| 郧西县| 崇信县| 长汀县| 图们市| 舟曲县| 临夏市| 绍兴县| 磐石市| 德惠市| 卢湾区| 象州县| 黑水县| 临漳县| 吴堡县| 兴城市| 娱乐| 临西县| 龙门县| 建瓯市| 沙坪坝区| 临沭县| 巴林左旗| 申扎县| 香港 | 江津市| 济阳县| 和田县| 朔州市| 应城市| 运城市| 忻州市| 成安县| 富川| 得荣县| 庆云县| 织金县| 涞源县| 田阳县| 满洲里市| 汾阳市|