加速数据读取的利器——缓存

定义
缓存,在我们日常开发中是必不可少的一种解决性能问题的方法。简单的说,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阅读 219,701评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,649评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,037评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,994评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,018评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,796评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,481评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,370评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,868评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,014评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,153评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,832评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,494评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,039评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,437评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,131评论 2 356

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,673评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,195评论 25 707
  • 今天输液第九天,昨天第八天,前天第七天,是星期一,新换的车牌限号,豆豆履新第一天,爆发了激烈的争吵,我自己做完盐浴...
    宁小静的日记阅读 262评论 0 0
  • 说起我对绘画的学习,是源于对国画的看法。我以前在学习书法的时候,每天都会在一个叫墨池的软件上打自己写的字,与一些墨...
    辉高阅读 481评论 4 11
  • 最近在用Eclipse写C++程序,今天把Eclipse设置上踩过的坑总结一下 1. program 'g++' ...
    张二虎阅读 1,118评论 0 0