加速數據讀取的利器——緩存

定義
緩存,在我們日常開發中是必不可少的一種解決性能問題的方法。簡單的說,cache 就是為了提升系統性能而開辟的一塊內存空間。

作用
緩存的主要作用是暫時在內存中保存業務系統的數據處理結果,并且等待下次訪問使用。在日常開發的很多場合,由于受限于硬盤IO的性能或者我們自身業務系統的數據處理和獲取可能非常費時,當我們發現我們的系統這個數據請求量很大的時候,頻繁的IO和頻繁的邏輯處理會導致硬盤和CPU資源的瓶頸出現。緩存的作用就是將這些來自不易的數據保存在內存中,當有其他線程或者客戶端需要查詢相同的數據資源時,直接從緩存的內存塊中返回數據,這樣不但可以提高系統的響應時間,同時也可以節省對這些數據的處理流程的資源消耗,整體上來說,系統性能會有大大的提升。

常用使用場景:

  • 在高并發的數據庫訪問時,為了抗住數據庫并發連接壓力,將數據緩存起來,當有請求過來,直接返回數據;
  • 當應用中的數據,更新周期較長,而且每次都查數據庫的情況下,可以采用周期更新數據,從而有效減少數據庫無效的訪問,保證效率;

衡量指標
對于使用緩存來加速數據讀取的情況,一個很關鍵的指標是緩存命中率,因為如果緩存命中率比較低的話,就意味著還有不少的讀請求要回到數據庫中。

2. 介紹一種緩存機制

本節以JAVA重構時使用的緩存機制為例,講解我們日常工作中常用的一種比較高效的緩存機制。具體如下圖所示:

技術架構圖.png

以上緩存機制使用了一級緩存(Memcache) + 二級本地緩存(Guava)的二級緩存機制。

  • 其中,Memcache是一個高性能的分布式的內存對象緩存系統,通過在內存里維護一個統一的巨大的hash表,它能夠用來存儲各種格式的數據,包括圖像、視頻、文件以及數據庫檢索的結果等。簡單的說就是將數據調用到內存中,然后從內存中讀取,從而大大提高讀取速度。
  • Guava是一個全內存的本地緩存實現,它提供了線程安全的實現機制。能夠保證當多個請求獲取同一個key值時,只允許一個線程訪問服務器,其他線程阻塞等待(系統處理:直接返回,不用阻塞),這樣就能很好的減輕服務端壓力;另外,比起服務端向Memcache獲取緩存,本地緩存減少了網絡IO開銷,提升數據返回的速度,減少了客戶端響應的時間。

當然,使用Guava作為本地緩存,需要滿足以下的適用場景:

  • 愿意消耗一部分應用服務器的內存來提升速度;
  • 預料某些值會被多次調用;
  • 緩存數據不會超過內存總量;

那么,我們是否有疑問,反正,我這邊至少有2點疑問:

  1. 如何保證一級緩存和二級緩存的數據一致性;
  2. 如何保證一級緩存更新后,不同的服務器下的二級緩存都會進行同步更新;
    基于上面的問題,又因為我們面對的是一個黑盒的緩存框架,現有的日志無法直接驗證這2點,那么直接上代碼:
//Memcache緩存更新,然后刪掉本地緩存
 public void putCache(String key, Object value) {
        long startTime = System.currentTimeMillis();
        LOG.info("Start put cache, key = {}, value = {}", key, JSON.toJSONString(value));
        ReentrantLock lock = this.getLock(key);
        if(null != lock && lock.tryLock()) {
            try {
                CacheObject cacheObject = CacheObject.generateCacheObject(value);
         this.memcachedClient.set(key, cacheObject.getUnixExpireTimeInSeconds(), cacheObject);
               
 this.punishCacheRefreshEvent(JSON.toJSONString(Lists.newArrayList(new String[]{key})));
                LOG.info("Put data to Memcached cost {}ms", Long.valueOf(System.currentTimeMillis() - startTime));
            } finally {
                lock.unlock();
            }
        }

    }
//調zk刪本地緩存的方法
private void punishCacheRefreshEvent(String cacheKeyListStr) {
        long startTime = System.currentTimeMillis();
        this.curatorService.writeDataToNode("/localCache/event/delete", cacheKeyListStr);
        LOG.info("Punish cache refresh event to ZK cost {}ms, keys = {}", Long.valueOf(System.currentTimeMillis() - startTime), cacheKeyListStr);
    }

通過上面的代碼,可知更新緩存操作時,均有日志記錄。進入####-wrapper.log可進行驗證,上日志:

2017-06-15 10:54:03.120 [ItemListCacheTask-6-exe0] INFO  MemcachedService       - Punish cache refresh event to ZK cost 4ms, keys = ["master####_TAE_ITEM_LIST_BRAND_AREA_ID_2_174756"]
 2017-06-15 10:54:03.120 [ItemListCacheTask-6-exe0] INFO  MemcachedService       - Put data to Memcached cost 5ms
 2017-06-15 10:54:03.121 [pool-2-thread-1 ] INFO  MemcachedService       - Will delete caches : ["master####_TAE_ITEM_LIST_BRAND_AREA_ID_2_174756"]
 2017-06-15 10:54:03.214 [ItemListCacheTask-6-exe0] INFO  MemcachedService       - Start put cache, key = master####_TAE_ITEM_LIST_BRAND_AREA_ID_2_174826, value = {"index_config":{"brandAreaButton":0,"brandAreaRedirectUrl":"","brandSpecialData":{"indexTopTitle":"測試我","style":"2"},"categorySwitch":1,"checkMaxVersion":"","checkMinVersion":"","couponSloganPicture":"/taobao/web_shopGuide591948637e93c_960_1280.jpg","curTab":1,"currentAndroidVersion":"","currentIosVersion":"","defaultChannelId":"","displayActivityName":1,"displayTab2Dot":0,"footerPicture":"/taobao/web_shopGuide5912d39b6305e_248_88.gif","globalPromotionData":{"image":"/taobao/web_shopGuide59194870cb96c_960_1280.jpg"},"historyDescript":"今天的新品都完了喲~\r\n看看之前錯過了什么吧!","indexTips":"每天百款新品,10點更新,10點","isIndex":1,"itemPromotionStyleData":{"autoIncrement":"1","data":[{"id":"1","image":"/taobao/web_shopGuide591007923bbfe_138_138.png","name":"測試"}]},"leftIcon":1,"listStyle":1,"loadingImage":"/taobao/web_shopGuide591409020acac_224_226.jpg","noneIndex":1,"pictureTag":{"eveningMall":"/taobao/web_shopGuide591007cb5fe2b_68_68.png","superBurst":"/taobao/web_shopGuide591007da338bb_68_68.png","timedSpike":"/taobao/web_shopGuide591007d2c8544_68_68.png","today":"/taobao/web_shopGuide59194893e88b4_68_68.png"},"playTimes":12345667,"redirectCart":0,"refreshText":"每天10點,有新鮮貨出沒\r\n每天20點,有新鮮貨出沒\r\n每天30點,有新鮮貨出沒\r\n全場低折,你要的都在這\r\n達人推薦,買什么都知道\r\n柚幣福利,驚喜不停\r\n柚姨幫你砍價啦~","shareSwitch":0,"sloganPicture":"/taobao/web_shopGuide5923f38252a22_750_350.jpg","tab1Title":"","tab2Title":"","useTbkSdk":1},"next_brand_area_data":{"1":{"tae_item":{"activityId":178382,"areaStyle":1,"brandAreaId":174560,"customTag":"","description":"","endAt":1513735200000,"isCoin":0,"isShowHot":1,"keplerSaleItemCount":1,"lowDiscount":0.0,"maxVersion":"","minVersion":"","name":"7號最后瘋搶","oneItemPic":"","orderCount":0,"ordinal":23,"picture":"","platform":"","promotionText":"","saleItemCount":49,"sortType":0,"tagIds":""},"common_item":{"$ref":"$.next_brand_area_data.1.tae_item"}},"2":{"tae_item":{},"common_item":{"activityId":170517,"areaStyle":1,"brandAreaId":166847,"customTag":"","description":"","endAt":1524103200000,"isCoin":0,"isShowHot":1,"keplerSaleItemCount":2,"lowDiscount":0.0,"maxVersion":"","minVersion":"","name":"京東用品尿褲櫥窗","oneItemPic":"","orderCount":0,"ordinal":0,"picture":"","platform":"","promotionText":"","saleItemCount":2,"sortType":0,"tagIds":""}}},"item_list":[],"brand_area_info":{"appId":2,"areaTimerType":1,"bannerPictureArray":[{"brandAreaId":174826,"createdAt":1496217409000,"height":"476","id":13816,"linkType":10017,"linkValue":"","type":1,"url":"/taobao/web_brand_area592e76550b369_361_476.jpg","width":"361"},{"brandAreaId":174826,"createdAt":1496217409000,"height":"440","id":13817,"linkType":0,"linkValue":"","type":1,"url":"/taobao/web_brand_area592e76640658a_1280_440.jpg","width":"1280"}],"bpLinkType":10017,"bpLinkValue":"","brandAreaType":1,"brandPicture":"/taobao/web_brand_area592e76550b369_361_476.jpg","description":"專場簡介:123","endAt":1504836000000,"id":174826,"isActive":1,"isCoin":0,"isShowHot":1,"isTop":0,"isZijian":0,"listStyle":2,"name":"淑錚專場2","oridinal":0,"payErrorMessage":"","promotionText":"促銷信息:促銷會顯示成什么樣子呢?","saleItemCount":0,"sortType":1,"startAt":1496217380000,"timerType":2,"topItems":"null","topTag":"頂部標簽1"}}, expireTimeInSeconds = 2592000

日志路徑:
[jumpserver@master-app-01 ####-####]$ pwd
/data/log/####-####

3. 問題定位

3.1 定位問題的步驟

工作中,經常會碰到運營后臺操作后或清除緩存后,接口數據未更新的情況。此時,我們需要掌握定位此類問題的技能,還是以JAVA端重構接口的緩存機制為例。

下圖展示了一種刪key類型的清緩存方式,一種_cache類型的更新緩存機制:

后臺-緩存.png
  • 第一步:關注后臺發送的topic消息

操作后臺進行數據更新后,如上圖所示,針對2種類型的更新緩存模式,會發送MQ消息,即JAVA端訂閱的topic消息。日志路徑:

[jumpserver@master-app-01 ####-####]$ cd /data/web/log/

具體到以下2種類型,具體分析:

  1. 刪key類型的接口:日志為log下的refresh_cache文件夾下的info.log,如下:
[jumpserver@master-app-01 refresh_cache]$ pwd
/data/web/log/refresh_cache
[jumpserver@master-app-01 refresh_cache]$ ls
exception          info.20170606.log  info.20170612.log  my_item_info               refresh_by_methods                   tae_item_list_cache
info.20170601.log  info.20170607.log  info.20170613.log  own_item_list_cache        tae_active_item_list_cache
info.20170602.log  info.20170608.log  info.20170614.log  phone_recharge             tae_category_client_item_list_cache
info.20170605.log  info.20170609.log  info.20170615.log  recommend_item_pool_cache  tae_category_client_list_cache
  1. _cache類型的接口:針對具體的接口,日志放在不同的文件夾下,如tae_brand_list相關的首頁、專場等在refresh_brand_area文件夾下,專場分類列表在refresh_category_client_item_list文件夾下。以首頁專場為例,如下:
[jumpserver@master-app-01 refresh_brand_area]$ pwd
/data/web/log/refresh_brand_area
[jumpserver@master-app-01 refresh_brand_area]$ ls
info.20170511.log  info.20170516.log  info.20170521.log  info.20170526.log  info.20170531.log  info.20170605.log  info.20170610.log  info.20170615.log
info.20170512.log  info.20170517.log  info.20170522.log  info.20170527.log  info.20170601.log  info.20170606.log  info.20170611.log
info.20170513.log  info.20170518.log  info.20170523.log  info.20170528.log  info.20170602.log  info.20170607.log  info.20170612.log
info.20170514.log  info.20170519.log  info.20170524.log  info.20170529.log  info.20170603.log  info.20170608.log  info.20170613.log
info.20170515.log  info.20170520.log  info.20170525.log  info.20170530.log  info.20170604.log  info.20170609.log  info.20170614.log

打開日志文件,后臺操作刪key后,打印日志如下:

info | 16687 | 1497496242.3234 | 2017:06:15 11:10:42 | 0.002588987350 | 成功發送消息: {"data":[{"v":1,"app_id":1,"app_key":"001","method":"new_user_special_item","clear_cache":1,"delete_key":true,"append_id":"174844"},{"v":1,"app_id":7,"app_key":"007","method":"new_user_special_item","clear_cache":1,"delete_key":true,"append_id":"174844"}],"time":"2017-06-15 11:10:42","cachebuildUrl":"http:\/\/test.cachebuild.master.youzibuy.com\/router\/rest","uuid":"REFRESH_CACHE__5941fab24ef424.48576463","cache_prefix":"master"}
  • 第二步:關注JAVA端監聽到消息后,是否進行了處理

JAVA端的日志路徑:

/data/log/####-####
我們關注日志文件####-mq-listener.log即可。在接收到后臺發送的topic消息后,JAVA端會向Memcache系統發送相關的刪key or 更新key的請求。

[jumpserver@master-app-01 ####-####]$ tail -f ####-mq-listener.log
 2017-06-15 11:10:42.322 [cacheDeleteContainer-1] WARN  CacheDeleteCommandListener - Current instance is not leader, will not handle cache refresh request from mq.
 2017-06-15 11:10:42.337 [cacheDeleteContainer-1] WARN  AbstractListener       - receieve magic message key ID:master-app-01-47896-1496894034686-3:10103:-1:1:397 content : {"data":[{"v":1,"app_id":1,"app_key":"001","method":"new_user_special_item","clear_cache":1,"delete_key":true,"append_id":"174842"},{"v":1,"app_id":7,"app_key":"007","method":"new_user_special_item","clear_cache":1,"delete_key":true,"append_id":"174842"}],"time":"2017-06-15 11:10:42","cachebuildUrl":"http:\/\/####.master.####.com\/router\/rest","uuid":"REFRESH_CACHE__5941fab24c4025.73699813","cache_prefix":"master"}
 2017-06-15 11:10:42.338 [cacheDeleteContainer-1] WARN  CacheDeleteCommandListener - #### del cache from mq
 2017-06-15 11:10:42.338 [cacheDeleteContainer-1] WARN  CacheDeleteCommandListener - Current instance is not leader, will not handle cache refresh request from mq.
  • 第三步:緩存更新是否成功

該部分監控,我們可以借助MQ平臺:

http://...:**/admin/queues.jsp;jsessionid=gvurz20v706dev474xwsgd95

image.png

我們在確認上面第二部JAVA端進行了消息處理后,可關注Memcache系統有沒有更新緩存成功。具體的緩存類型,關注具體的緩存處理,其中:

  • 刪key ——> refresh_cache_new_java
  • _cache ——> refresh_cache_java

關注點:

  1. Number of Consumers,即消費消息的數量是否>0,如果等于0,那么JAVA端的緩存構建系統出現故障。
  2. Messages Enqueued和Messages Dequeued的數量分別+1,則表示緩存更新成功,若Messages Enqueued數量+1,而Messages Dequeued數量不變,則表示緩存更新失敗。
image.png

3.2 配置相關

從后臺發送topic消息,到JAVA端接收,二者必須保證發送的和接收的一致性。這樣才能夠最終刷緩存成功。

  • PHP端后臺配置

我們一般關注后臺發送的topic消息前綴,如操作主干后臺,通過日志可知前綴為master。

info | 26698 | 1497521442.5619 | 2017:06:15 18:10:42 | 0.008535861969 | 成功發送消息: 
{"data":
[
{"v":1,"app_id":1,"app_key":"001","method":"new_user_special_item","clear_cache":1,"delete_key":true,"append_id":"174847"},{"v":1,"app_id":7,"app_key":"007","method":"new_user_special_item","clear_cache":1,"delete_key":true,"append_id":"174847"}
],
"time":"2017-06-1518:10:42",
"cachebuildUrl":"http:\/\/test.cachebuild.master.youzibuy.com\/router\/rest","uuid":"REFRESH_CACHE__59425d22892ac2.15914753",
"cache_prefix":"master"}
  • JAVA端配置

我們一般關注JAVA端接收消息的前綴,這個需要和后臺發送消息的前綴保持一致。JAVA端的配置平臺:http://dev.#####.####.com/modifyFile.html?configId=183
關注disconf-managed-config.properties下的緩存前綴,如下:

memcache.key.prifix=master
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,818評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,711評論 25 708
  • 今天輸液第九天,昨天第八天,前天第七天,是星期一,新換的車牌限號,豆豆履新第一天,爆發了激烈的爭吵,我自己做完鹽浴...
    寧小靜的日記閱讀 272評論 0 0
  • 說起我對繪畫的學習,是源于對國畫的看法。我以前在學習書法的時候,每天都會在一個叫墨池的軟件上打自己寫的字,與一些墨...
    輝高閱讀 504評論 4 11
  • 最近在用Eclipse寫C++程序,今天把Eclipse設置上踩過的坑總結一下 1. program 'g++' ...
    張二虎閱讀 1,132評論 0 0