定義:
緩存,在我們?nèi)粘i_發(fā)中是必不可少的一種解決性能問題的方法弥激。簡(jiǎn)單的說进陡,cache 就是為了提升系統(tǒng)性能而開辟的一塊內(nèi)存空間。
作用:
緩存的主要作用是暫時(shí)在內(nèi)存中保存業(yè)務(wù)系統(tǒng)的數(shù)據(jù)處理結(jié)果微服,并且等待下次訪問使用趾疚。在日常開發(fā)的很多場(chǎng)合,由于受限于硬盤IO的性能或者我們自身業(yè)務(wù)系統(tǒng)的數(shù)據(jù)處理和獲取可能非常費(fèi)時(shí)以蕴,當(dāng)我們發(fā)現(xiàn)我們的系統(tǒng)這個(gè)數(shù)據(jù)請(qǐng)求量很大的時(shí)候糙麦,頻繁的IO和頻繁的邏輯處理會(huì)導(dǎo)致硬盤和CPU資源的瓶頸出現(xiàn)。緩存的作用就是將這些來自不易的數(shù)據(jù)保存在內(nèi)存中丛肮,當(dāng)有其他線程或者客戶端需要查詢相同的數(shù)據(jù)資源時(shí)赡磅,直接從緩存的內(nèi)存塊中返回?cái)?shù)據(jù),這樣不但可以提高系統(tǒng)的響應(yīng)時(shí)間宝与,同時(shí)也可以節(jié)省對(duì)這些數(shù)據(jù)的處理流程的資源消耗焚廊,整體上來說冶匹,系統(tǒng)性能會(huì)有大大的提升。
常用使用場(chǎng)景:
- 在高并發(fā)的數(shù)據(jù)庫訪問時(shí)咆瘟,為了抗住數(shù)據(jù)庫并發(fā)連接壓力嚼隘,將數(shù)據(jù)緩存起來,當(dāng)有請(qǐng)求過來袒餐,直接返回?cái)?shù)據(jù)飞蛹;
- 當(dāng)應(yīng)用中的數(shù)據(jù),更新周期較長(zhǎng)匿乃,而且每次都查數(shù)據(jù)庫的情況下桩皿,可以采用周期更新數(shù)據(jù),從而有效減少數(shù)據(jù)庫無效的訪問幢炸,保證效率泄隔;
衡量指標(biāo):
對(duì)于使用緩存來加速數(shù)據(jù)讀取的情況,一個(gè)很關(guān)鍵的指標(biāo)是緩存命中率宛徊,因?yàn)槿绻彺婷新时容^低的話佛嬉,就意味著還有不少的讀請(qǐng)求要回到數(shù)據(jù)庫中。
2. 介紹一種緩存機(jī)制
本節(jié)以JAVA重構(gòu)時(shí)使用的緩存機(jī)制為例闸天,講解我們?nèi)粘9ぷ髦谐S玫囊环N比較高效的緩存機(jī)制暖呕。具體如下圖所示:
以上緩存機(jī)制使用了一級(jí)緩存(Memcache) + 二級(jí)本地緩存(Guava)的二級(jí)緩存機(jī)制。
- 其中苞氮,Memcache是一個(gè)高性能的分布式的內(nèi)存對(duì)象緩存系統(tǒng)湾揽,通過在內(nèi)存里維護(hù)一個(gè)統(tǒng)一的巨大的hash表,它能夠用來存儲(chǔ)各種格式的數(shù)據(jù)笼吟,包括圖像库物、視頻、文件以及數(shù)據(jù)庫檢索的結(jié)果等贷帮。簡(jiǎn)單的說就是將數(shù)據(jù)調(diào)用到內(nèi)存中戚揭,然后從內(nèi)存中讀取,從而大大提高讀取速度撵枢。
- Guava是一個(gè)全內(nèi)存的本地緩存實(shí)現(xiàn)民晒,它提供了線程安全的實(shí)現(xiàn)機(jī)制。能夠保證當(dāng)多個(gè)請(qǐng)求獲取同一個(gè)key值時(shí)锄禽,只允許一個(gè)線程訪問服務(wù)器潜必,其他線程阻塞等待(系統(tǒng)處理:直接返回,不用阻塞)沟绪,這樣就能很好的減輕服務(wù)端壓力刮便;另外,比起服務(wù)端向Memcache獲取緩存绽慈,本地緩存減少了網(wǎng)絡(luò)IO開銷恨旱,提升數(shù)據(jù)返回的速度,減少了客戶端響應(yīng)的時(shí)間坝疼。
當(dāng)然搜贤,使用Guava作為本地緩存,需要滿足以下的適用場(chǎng)景:
- 愿意消耗一部分應(yīng)用服務(wù)器的內(nèi)存來提升速度钝凶;
- 預(yù)料某些值會(huì)被多次調(diào)用仪芒;
- 緩存數(shù)據(jù)不會(huì)超過內(nèi)存總量;
那么耕陷,我們是否有疑問掂名,反正,我這邊至少有2點(diǎn)疑問:
- 如何保證一級(jí)緩存和二級(jí)緩存的數(shù)據(jù)一致性哟沫;
- 如何保證一級(jí)緩存更新后饺蔑,不同的服務(wù)器下的二級(jí)緩存都會(huì)進(jìn)行同步更新;
基于上面的問題嗜诀,又因?yàn)槲覀兠鎸?duì)的是一個(gè)黑盒的緩存框架猾警,現(xiàn)有的日志無法直接驗(yàn)證這2點(diǎn),那么直接上代碼:
//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();
}
}
}
//調(diào)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);
}
通過上面的代碼发皿,可知更新緩存操作時(shí),均有日志記錄拂蝎。進(jìn)入####-wrapper.log可進(jìn)行驗(yàn)證穴墅,上日志:
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":"測(cè)試我","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看看之前錯(cuò)過了什么吧!","indexTips":"每天百款新品温自,10點(diǎn)更新,10點(diǎn)","isIndex":1,"itemPromotionStyleData":{"autoIncrement":"1","data":[{"id":"1","image":"/taobao/web_shopGuide591007923bbfe_138_138.png","name":"測(cè)試"}]},"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點(diǎn)玄货,有新鮮貨出沒\r\n每天20點(diǎn),有新鮮貨出沒\r\n每天30點(diǎn)捣作,有新鮮貨出沒\r\n全場(chǎng)低折誉结,你要的都在這\r\n達(dá)人推薦,買什么都知道\r\n柚幣福利券躁,驚喜不停\r\n柚姨幫你砍價(jià)啦~","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號(hào)最后瘋搶","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":"京東用品尿褲?rùn)淮?,"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":"專場(chǎng)簡(jiǎn)介:123","endAt":1504836000000,"id":174826,"isActive":1,"isCoin":0,"isShowHot":1,"isTop":0,"isZijian":0,"listStyle":2,"name":"淑錚專場(chǎng)2","oridinal":0,"payErrorMessage":"","promotionText":"促銷信息:促銷會(huì)顯示成什么樣子呢惩坑?","saleItemCount":0,"sortType":1,"startAt":1496217380000,"timerType":2,"topItems":"null","topTag":"頂部標(biāo)簽1"}}, expireTimeInSeconds = 2592000
日志路徑:
[jumpserver@master-app-01 ####-####]$ pwd
/data/log/####-####
3. 問題定位
3.1 定位問題的步驟
工作中,經(jīng)常會(huì)碰到運(yùn)營(yíng)后臺(tái)操作后或清除緩存后也拜,接口數(shù)據(jù)未更新的情況以舒。此時(shí),我們需要掌握定位此類問題的技能慢哈,還是以JAVA端重構(gòu)接口的緩存機(jī)制為例蔓钟。
下圖展示了一種刪key類型的清緩存方式,一種_cache類型的更新緩存機(jī)制:
-
第一步:關(guān)注后臺(tái)發(fā)送的topic消息
操作后臺(tái)進(jìn)行數(shù)據(jù)更新后卵贱,如上圖所示滥沫,針對(duì)2種類型的更新緩存模式侣集,會(huì)發(fā)送MQ消息,即JAVA端訂閱的topic消息兰绣。日志路徑:
[jumpserver@master-app-01 ####-####]$ cd /data/web/log/
具體到以下2種類型世分,具體分析:
- 刪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
- _cache類型的接口:針對(duì)具體的接口缀辩,日志放在不同的文件夾下臭埋,如tae_brand_list相關(guān)的首頁、專場(chǎng)等在refresh_brand_area文件夾下臀玄,專場(chǎng)分類列表在refresh_category_client_item_list文件夾下瓢阴。以首頁專場(chǎng)為例,如下:
[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
打開日志文件健无,后臺(tái)操作刪key后荣恐,打印日志如下:
info | 16687 | 1497496242.3234 | 2017:06:15 11:10:42 | 0.002588987350 | 成功發(fā)送消息: {"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"}
-
第二步:關(guān)注JAVA端監(jiān)聽到消息后,是否進(jìn)行了處理
JAVA端的日志路徑:
/data/log/####-####
我們關(guān)注日志文件####-mq-listener.log即可睬涧。在接收到后臺(tái)發(fā)送的topic消息后募胃,JAVA端會(huì)向Memcache系統(tǒng)發(fā)送相關(guān)的刪key or 更新key的請(qǐng)求。
[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.
-
第三步:緩存更新是否成功
該部分監(jiān)控畦浓,我們可以借助MQ平臺(tái):
http://...:**/admin/queues.jsp;jsessionid=gvurz20v706dev474xwsgd95
我們?cè)诖_認(rèn)上面第二部JAVA端進(jìn)行了消息處理后痹束,可關(guān)注Memcache系統(tǒng)有沒有更新緩存成功。具體的緩存類型讶请,關(guān)注具體的緩存處理祷嘶,其中:
- 刪key ——> refresh_cache_new_java
- _cache ——> refresh_cache_java
關(guān)注點(diǎn):
- Number of Consumers,即消費(fèi)消息的數(shù)量是否>0夺溢,如果等于0论巍,那么JAVA端的緩存構(gòu)建系統(tǒng)出現(xiàn)故障。
- Messages Enqueued和Messages Dequeued的數(shù)量分別+1风响,則表示緩存更新成功嘉汰,若Messages Enqueued數(shù)量+1,而Messages Dequeued數(shù)量不變状勤,則表示緩存更新失敗鞋怀。
3.2 配置相關(guān)
從后臺(tái)發(fā)送topic消息,到JAVA端接收持搜,二者必須保證發(fā)送的和接收的一致性密似。這樣才能夠最終刷緩存成功。
-
PHP端后臺(tái)配置
我們一般關(guān)注后臺(tái)發(fā)送的topic消息前綴葫盼,如操作主干后臺(tái)残腌,通過日志可知前綴為master。
info | 26698 | 1497521442.5619 | 2017:06:15 18:10:42 | 0.008535861969 | 成功發(fā)送消息:
{"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端配置
我們一般關(guān)注JAVA端接收消息的前綴,這個(gè)需要和后臺(tái)發(fā)送消息的前綴保持一致抛猫。JAVA端的配置平臺(tái):http://dev.#####.####.com/modifyFile.html?configId=183
關(guān)注disconf-managed-config.properties下的緩存前綴蟆盹,如下:
memcache.key.prifix=master