小技巧:用二级缓存提高缓存命中率和内存使用效率

起因

一直都没找到二级缓存在php中应用的比较好的资料和案例,由于范凯RobbinWeb 应用的缓存设计模式和Hibernate二级缓存的启示,记下这篇二级缓存在Eloquent ORM中的应用。

过程

比如博客的首页调用最新的20篇文章,相信不少同学在刚开始使用缓存的时候,会写下如下代码:

# 控制器

public function index()

{

$articles = Article::latestArticles(20);

return view('articles.index', ['articles' => $articles]);

}

# 模型

class Article extends Model

{

public static function latestArticles($amount = 20)

{

return Cache::remember('articles:latest', 10, function () use ($amount) {

return static::latest('id')->take($amount)->get();

});

}

}

当然,模型中还能预加载每篇文章的分类,作者和tag信息,看起来没有任何问题,而且非常符合人类直觉。但是,放大到全站缓存来看,还是有很大的改善空间。

首先,首页缓存的是一个包含20个article对象的集合,集合的每一个单独的article对象除了在首页出现,还会在分类、作者和tag等列表页出现,还有文章详情页,而缓存的集合数据没办法在这些页面间共用,重复缓存大量相同的article对象是对内存资源的很大浪费,要是article中的text字段content没有单独拆分出去,内存浪费得就更严重了。

其次,不像详情页数据改动很少,首页作为列表页来说,更新频率很高,设置的缓存时间比较短,一般是分钟级别,缓存命中率并不高。

为了有效解决这两个问题,二级缓存就派上用场了,先说下自己对二级缓存的理解。

一级缓存可以看成是数据库里存的数据的一个镜像,只不过把数据从数据库搬到内存,一个key对应一条记录。key一般为表的标识符,比如key为articles:1存的value就是id=1的article对象。一级缓存时间可以设得比较长,甚至forever也行,对象修改删除时,只要删除对应的key就行。

二级缓存可以看成业务逻辑的缓存,首页最新20条文章 就属于业务逻辑,只缓存这20条文章的id,极大地节省了内存占用。等需要用到具体的数据再去一级缓存取,一级缓存没有才去查询数据库,由于都是主键查询,不会造成表的描述,查询效率非常高。即使二级缓存很快过期,一级缓存也不会失效。

个人觉得理解二级缓存最难的是要接受n+1查询这点,这个问题争议很大,明明各种ORM为了避免n+1使用了预加载,我们反而要抛弃它。包括我当初阅读范凯的《Web 应用的缓存设计模式》也心存疑惑,直到去了解了Hibernate二级缓存机制和自己在项目中的实践发现,还真是他说的那样。

拆分n+1条查询的方式,看起来似乎非常违反大家的直觉,但实际上这是真理,我实践经验证明:数据库服务器的瓶颈往往是磁盘IO,而不是SQL并发数量。因此 拆分n+1条查询本质上是以增加n条SQL语句为代价,简化复杂SQL,换取数据库服务器磁盘IO的降低 当然这样做以后,对于ORM来说,有额外的好处,就是可以高效的使用缓存了。

使用二级缓存来重构latestArticles方法

public static function latestArticles($amount = 20)

{

// 二级缓存

$ids = Cache::remember('articles:latest:ids', 10, function () use ($amount) {

return static::latest('id')->take($amount)->pluck('id');

});

return $ids->map(function ($id) {

// 一级缓存

return static::findById($id);

});

}

public static function findById($id)

{

return Cache::rememberForever("articles:{$id}", function () use ($id) {

return static::find($id);

});

}

除了返回Collection,还可以返回Generator。

public static function latestArticles($amount = 20)

{

// 二级缓存

$ids = Cache::remember('articles:latest:ids', 10, function () use ($amount) {

return static::latest('id')->take($amount)->pluck('id');

});

foreach ($ids as $id) {

// 一级缓存

yield static::findById($id);

}

}

吐槽

在专栏发布界面,好不容易写到这里,结果手贱划了下浏览器刷新的鼠标手势,内心瞬间奔崩,砸电脑的心都有了 :rage:。虽然看提示有自动保存,可只有标题被保存了,内容还是空空如也。转眼想想历史上那些不小心毁了书稿写出世界名著的作家,自己也只有硬着头皮把内容回忆出来,所以后面的内容就一笔带过了。

更新与删除

一级缓存的更新和删除可能通过模型的updated和deleted事件来清除对应的缓存。二级缓存由于缓存时间比较短,影响不大。

关联关系

关联模型的缓存可能通过accessor来设置一个虚拟的属性来设置,比如在Article模型与Content模型是一对一的关系。

在Article中:

// 一对一关联

public function content()

{

return $this->hasOne(Content::class);

}

// contents表字段: article_id, body

public function getContentAttribute()

{

return Cache::rememberForever("contents:{$this->id}", function () {

return $this->content->body;

});

}

Java高架构师、分布式架构、高可扩展、高性能、高并发、性能优化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式项目实战学习架构师视频免费获取架构群:854180697

群链接:加群链接

写在最后:欢迎留言讨论,加关注,持续更新!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,642评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,604评论 18 399
  • 一. Java基础部分.................................................
    wy_sure阅读 3,807评论 0 11
  • 尊敬的小圆老师早上好,尊敬的守一老师早上好,尊敬的明慎老师早上好。我是广西译文妈,朋至老师早课收获,1.遇到问题需...
    译文_阅读 124评论 0 0
  • 如果说思科对路由器和宽带的创新,让Network变成了Internet,带来了1990年开始的互联网浪潮。随着20...
    钛钰麒麟阅读 412评论 0 1