OpenResty 从入门到实战
温铭
OpenResty 软件基金会第一任主席,Apache APISIX 项目 VP
20903 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 52 讲
结束语 (1讲)
OpenResty 从入门到实战
15
15
1.0x
00:00/00:00
登录|注册

41 | lua-resty-* 封装,让你远离多级缓存之痛

留下思考题:共享字典这一层缓存是必须的吗?如果只用 lrucache 是否可以呢?
通过介绍的两个封装库,帮助更好地理解缓存
有了多层缓存,服务端的性能得到最大限度的保证
lua-resty-mlcache
lua-resty-memcached-shdict
总结
缓存封装库

该思维导图由 AI 生成,仅供参考

你好,我是温铭。
前面两节课中,我们已经学习了 OpenResty 中的缓存,以及容易出错的缓存风暴问题,这些都是属于偏基础的一些知识。在实际的项目开发中,开发者自然更希望有一个已经把各种细节处理好并隐藏起来的开箱即用的库,可以拿来直接开发业务代码。
这其实就是分工的一个好处,基础组件的开发者,重心在于架构灵活、性能极致、代码稳定,并不需要关心上层业务逻辑;而应用层的工程师,更关心的是业务实现和快速迭代,并不希望被底层的各种技术细节分心。这中间的鸿沟,就需要有一些封装库来填平了。
OpenResty 中的缓存,也面临一样的问题。共享字典和 lru 缓存足够稳定和高效,但需要处理太多的细节。如果没有一些好用的封装,那么到达应用开发工程师的“最后一公里”,就会变得比较痛苦。这个时候,就要体现社区的重要性了。一个活跃的社区,会主动去发现鸿沟,并迅速地填平。

lua-resty-memcached-shdict

让我们回到缓存的封装上来。lua-resty-memcached-shdict 是 OpenResty 官方的一个项目,它使用 shared dict 为 memcached 做了一层封装,处理了缓存风暴和过期数据等细节。如果你的缓存数据正好存储在后端的 memcached 中,那么你可以尝试使用这个库。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

OpenResty中的缓存问题一直是开发者头疼的难题,但幸运的是,有一个名为`lua-resty-memcached-shdict`的封装库可以帮助解决这一问题。该库使用shared dict为memcached做了一层封装,处理了缓存风暴和过期数据等细节。虽然它并不完美,但仍然是一个有用的工具。然而,它暴露的接口参数过多,需要用户填写11个必填参数和7个选填参数,这使得使用起来并不友好。此外,文档中还提到了进一步的优化方向,包括使用`lua-resty-lrucache`增加worker层的缓存,以及使用`ngx.timer`进行异步的缓存更新操作。尽管这些优化方向有一定的局限性,但仍值得开发者们深入思考。因此,该封装库为开发者提供了一种解决缓存问题的思路,同时也为社区贡献提供了机会。 另一个被广泛使用的缓存封装是`lua-resty-mlcache`,它实现了多层缓存机制,使用shared dict和lua-resty-lrucache。该库隐藏了多层缓存的复杂逻辑,使得开发者只需使用mlcache对象获取缓存并设置缓存失效后的回调函数即可。其内部架构将数据分为三层,即L1、L2和L3,分别对应lrucache、shared dict和外部数据源。尽管mlcache已经实现得比较完美,但在实际使用中仍存在数据的序列化和反序列化问题。不过,mlcache已经考虑到了这种情况,并提供了可选的函数`l1_serializer`,用于处理L2提升到L1时对数据的处理。总的来说,mlcache是一个功能强大且写得非常详尽的缓存库,为开发者提供了解决缓存问题的有效工具。 有了多层缓存,服务端的性能才能得到最大限度的保证,而这中间又隐藏了很多的细节。通过介绍`lua-resty-memcached-shdict`和`lua-resty-mlcache`这两个封装库,本文希望帮助读者更好地理解缓存。最后留下一个思考题,共享字典这一层缓存是否是必须的?如果只用lrucache是否可以呢?欢迎读者留言分享观点,也欢迎分享本文,与更多人一起交流和进步。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《OpenResty 从入门到实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(9)

  • 最新
  • 精选
  • Shliesce
    在实际的生产应用中,我认为 shared dict 这一层缓存是必须的,貌似大家都只记得lrucache的好,数据格式没限制,不需要反序列化,不需要根据 k/v 体积算内存空间,worker 间独立不相互争抢,没有读写锁,性能高云云,但是却忘记了它最致命的一个弱点就是 lrucache 的生命周期是跟着 worker 走的,每当nginx reload 时,这部分缓存会全部丢失,这时候如果没有 shared dict,那 L3 的数据源分分钟被打挂,当然这是并发比较高的情况下,但是既然用到了缓存,说明业务体量肯定不会小。。不知道我的这个观点对吗?

    作者回复: 大部分情况下确实如你所说,共享字典在 reload 的时候不会丢失。也有一种特例,如果在 init 阶段或者init_worker阶段就能从 L3 主动获取到所有数据,那么只有 L1 其实也是可以接受的。

    2019-09-01
    5
  • manatee
    想请问下老师多次提到的timer数量限制,这个限制具体是多少呢,从哪里可以查到相关资料呢

    作者回复: lua_max_pending_timers 和 lua_max_running_timers 这两个指令来做控制

    2019-08-28
    3
    5
  • Shliesce
    接着我上面的这个观点,最近在设计“联动注册发现中心 动态更新 upstream 并缓存在进程内”的插件时,目前仅做了shared dict这一层的缓存,从初步的压测结果来看,单机 32C 的 cpu 平均使用率 30%可以达到 8w 的 qps,也不知道是不是错觉,性能比我们用 upsync 时还要好。。。 重点是在后续增加 lrucache 时,因为考虑到这种场景不存在key expired 的问题,主要是 value 变更比较频繁,且需要保证尽可能的数据一致性,像 mlcache 这种被动更新的逻辑就不太好实现,而且还要考虑到每次 reload 时的lrucache 全量 miss 的场景,最终评估下来我决定采用主动更新缓存的方案。同时因为 nginx 每次 reload 时都会加载 init_by_lua*阶段,所以我打算把主动更新的逻辑做到 init 阶段,借助 master fork worker 时继承内存空间的逻辑提前预热缓存;另外就是每次更新 shared dict 时同步更新 lrucache。暂时还不确定这个方案有没有什么坑,老师能帮忙提几条建议吗?

    作者回复: 没有明白为什么把主动更新的逻辑放在 init 阶段呢? 你可以在 init 阶段获取全量数据,通过长连接监听事件通知,去获取增量的数据。 这时候其实你可以只用 lru 缓存,去掉共享字典这个 L2 缓存。 APISIX(https://github.com/iresty/apisix)也是这么实现的,它的数据源在 etcd,通过 etcd 的 watch 来获取上游的变更。

    2019-09-01
    3
  • 许童童
    共享字典这一层缓存不是必须的,但有这一层性能会高很多,因为lrucache 只能在单个worker中共享,而共享字典可以在整个server中共享,所以当一个worker中命中不了时,此时数据可能在另一个worker中存在,如果有server层的缓存,那命中率将大大提高。
    2019-08-28
    2
  • 🆕
    shared.DICT.get使用上下文限制了init*阶段,同样的还有resty.lock库中的部分api,在init阶段也可能会失效,所以想问一下mlcache 是否有同样的上下文使用限制?
    2020-06-23
    1
  • 豆腐居士
    老师好,使用http库访问网络的时候,dns解析这块怎么做 ?目前我的做法是在server里用resolver指定了多个dns IP地址!但是缓存失效后会出现解析域名大量解析超时的情况!tks
    2021-01-07
  • 张仕华
    多级缓存的更新类似cpu,必须有lrucache层的缓存失效信号,看了看文档也确实是这样。但其实还有个疑问,如何更新db中的数据然后让缓存失效呢?
    2020-01-17
  • 言身寸飞
    我觉得shared dict是必须的。如果只有L1的话,量级大的、缓存经常变化的应用的话work多的话后面的数据会有连接压力吧。
    2019-09-19
  • wusiration
    共享字典这一层缓存并非必须,只用lrucache同样可以满足需求。只不过多加一层L2缓存(共享字典),可以使得服务器在L1缓存未命中的情况下,通过多个worker之间的共享数据,以减少读库的次数。
    2019-08-28
收起评论
显示
设置
留言
9
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部