41 | lua-resty-* 封装,让你远离多级缓存之痛
该思维导图由 AI 生成,仅供参考
lua-resty-memcached-shdict
- 深入了解
- 翻译
- 解释
- 总结
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-015 - manatee想请问下老师多次提到的timer数量限制,这个限制具体是多少呢,从哪里可以查到相关资料呢
作者回复: lua_max_pending_timers 和 lua_max_running_timers 这两个指令来做控制
2019-08-2835 - 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-013 - 许童童共享字典这一层缓存不是必须的,但有这一层性能会高很多,因为lrucache 只能在单个worker中共享,而共享字典可以在整个server中共享,所以当一个worker中命中不了时,此时数据可能在另一个worker中存在,如果有server层的缓存,那命中率将大大提高。2019-08-282
- 🆕shared.DICT.get使用上下文限制了init*阶段,同样的还有resty.lock库中的部分api,在init阶段也可能会失效,所以想问一下mlcache 是否有同样的上下文使用限制?2020-06-231
- 豆腐居士老师好,使用http库访问网络的时候,dns解析这块怎么做 ?目前我的做法是在server里用resolver指定了多个dns IP地址!但是缓存失效后会出现解析域名大量解析超时的情况!tks2021-01-07
- 张仕华多级缓存的更新类似cpu,必须有lrucache层的缓存失效信号,看了看文档也确实是这样。但其实还有个疑问,如何更新db中的数据然后让缓存失效呢?2020-01-17
- 言身寸飞我觉得shared dict是必须的。如果只有L1的话,量级大的、缓存经常变化的应用的话work多的话后面的数据会有连接压力吧。2019-09-19
- wusiration共享字典这一层缓存并非必须,只用lrucache同样可以满足需求。只不过多加一层L2缓存(共享字典),可以使得服务器在L1缓存未命中的情况下,通过多个worker之间的共享数据,以减少读库的次数。2019-08-28