专栏名称: 51CTO技术栈
有趣 | 有料 | 有内涵,为您提供最优质的内容,愿我们一起悦享技术,成就人生。
目录
相关文章推荐
51好读  ›  专栏  ›  51CTO技术栈

缓存太香了!我的10年使用经验总结!

51CTO技术栈  · 公众号  · 程序员  · 2020-12-18 18:05

正文

送 福 利 啦

关注 HarmonyOS技术社区 ,回复 【鸿蒙】 定制T恤 (礼品不多,先到先得) ,还可以 免费下载 鸿蒙 入门资料


👇 扫码 立刻关注 👇

专注开源技术,共建鸿蒙生态


一位七牛的资深架构师曾经说过这样一句话:“Nginx+业务逻辑层+数据库+缓存层+消息队列,这种模型几乎能适配绝大部分的业务场景。


图片来自 Pexels


这么多年过去了,这句话或深或浅地影响了我的技术选择,以至于后来我花了很多时间去重点学习缓存相关的技术。


我在 10 年前开始使用缓存,从本地缓存、到分布式缓存、再到多级缓存,踩过很多坑。下面我结合自己使用缓存的历程,谈谈我对缓存的认识。


本地缓存


页面级缓存


我使用缓存的时间很早,2010 年左右使用过 OSCache,当时主要用在 JSP 页面中用于实现页面级缓存。


伪代码类似这样:
<cache:cache key="foobar" scope="session"> 
      some jsp content 
cache:cache>


中间的那段 JSP 代码将会以 key="foobar" 缓存在 session 中,这样其他页面就能共享这段缓存内容。


在使用 JSP 这种远古技术的场景下,通过引入 OSCache 之后 ,页面的加载速度确实提升很快。


但随着前后端分离以及分布式缓存的兴起,服务端的页面级缓存已经很少使用了。但是在前端领域,页面级缓存仍然很流行。


对象缓存


2011 年左右,开源中国的红薯哥写了很多篇关于缓存的文章。他提到:开源中国每天百万的动态请求,只用 1 台 4 Core 8G 的服务器就扛住了,得益于缓存框架 Ehcache。


这让我非常神往,一个简单的框架竟能将单机性能做到如此这般,让我欲欲跃试。


于是,我参考红薯哥的示例代码,在公司的余额提现服务上第一次使用了 Ehcache。


逻辑也很简单,就是将成功或者失败状态的订单缓存起来,这样下次查询的时候,不用再查询支付宝服务了。


伪代码类似这样:

添加缓存之后,优化的效果很明显 , 任务耗时从原来的 40 分钟减少到了 5~10 分钟。


上面这个示例就是典型的「对象缓存」,它是本地缓存最常见的应用场景。相比页面缓存,它的粒度更细、更灵活,常用来缓存很少变化的数据,比如:全局配置、状态已完结的订单等,用于提升整体的查询速度。


刷新策略


2018 年,我和我的小伙伴自研了配置中心,为了让客户端以最快的速度读取配置, 本地缓存使用了 Guava。


整体架构如下图所示:

那本地缓存是如何更新的呢?有两种机制:

  • 客户端启动定时任务,从配置中心拉取数据。

  • 当配置中心有数据变化时,主动推送给客户端。这里我并没有使用 websocket,而是使用了 RocketMQ Remoting 通讯框架。


后来我阅读了 Soul 网关的源码,它的本地缓存更新机制如下图所示, 共支持 3 种策略:

Zookeeper Watch 机制: soul-admin 在启动的时候,会将数据全量写入 Zookeeper,后续数据发生变更时,会增量更新 Zookeeper 的节点。


与此同时,soul-web 会监听配置信息的节点,一旦有信息变更时,会更新本地缓存。


Websocket 机制: Websocket 和 Zookeeper 机制有点类似,当网关与 admin 首次建立好 websocket 连接时,admin 会推送一次全量数据,后续如果配置数据发生变更,则将增量数据通过 websocket 主动推送给 soul-web。


Http 长轮询机制 :Http 请求到达服务端后,并不是马上响应,而是利用 Servlet 3.0 的异步机制响应数据。


当配置发生变化时,服务端会挨个移除队列中的长轮询请求,告知是哪个 Group 的数据发生了变更,网关收到响应后,再次请求该 Group 的配置数据。


不知道大家发现了没?

  • pull 模式必不可少

  • 增量推送大同小异


长轮询是一个有意思的话题 , 这种模式在 RocketMQ 的消费者模型也同样被使用,接近准实时,并且可以减少服务端的压力。


分布式缓存


关于分布式缓存, Memcached 和 Redis 应该是最常用的技术选型。相信程序员朋友都非常熟悉了,我这里分享两个案例。


合理控制对象大小及读取策略


2013 年,我服务一家彩票公司,我们的比分直播模块也用到了分布式缓存。当时,遇到了一个 Young GC 频繁的线上问题,通过 jstat 工具排查后,发现新生代每隔两秒就被占满了。


进一步定位分析,原来是某些 key 缓存的 value 太大了,平均在 300K 左右,最大的达到了 500K。这样在高并发下,就很容易导致 GC 频繁。


找到了根本原因后,具体怎么改呢?我当时也没有清晰的思路。于是,我去同行的网站上研究他们是怎么实现相同功能的,包括:360 彩票,澳客网。


我发现了两点:

  • 数据格式非常精简,只返回给前端必要的数据,部分数据通过数组的方式返回。

  • 使用 Websocket,进入页面后推送全量数据,数据发生变化推送增量数据。


再回到我的问题上,最终是用什么方案解决的呢?当时,我们的比分直播模块缓存格式是 JSON 数组,每个数组元素包含 20 多个键值对, 下面的 JSON 示例我仅仅列了其中 4 个属性。
[{
     "playId":"2399"






请到「今天看啥」查看全文