来这里找志同道合的小伙伴!
缓存是系统性能提升优先法宝, 在互联网应用系统中, 屡试不爽. 网上有很多资料介绍缓存理论及使用策略, 本文就不再涉及了, 今天简单将缓存做个归类, 重点分享以前在实际业务中碰到场景以及如何使用.
接下来主要分两部分介绍: 缓存分类与应用实践案例.
缓存分类
缓存一般有以下几类: 客户端, 浏览器, CDN 缓存, NGINX 缓存, 应用缓存及统一缓存(如 Redis).
▲ 缓存分类: 用户 ->数据层
客户端缓存 : 很少使用, 一般都是传统企业才会使用. 把不变化或很长时间才变化的数据按一定格式存储在客户端的本地文件中, 使用时通过 JS 读取解析使用, 延用了 C/S 结构的方式, 适合数据量很大业务且技术有所不足的开发.
浏览器缓存 : 这种形式使用很广泛, 极大地提升了用户体验, 但有时会出现没及时更新导致显示 "错误" 的信息. 把已经请求过的 web 资源 (如 html 页面, 图片, JS,CSS 等) 拷贝一份副本储存在浏览器中, 缓存会根据进来的请求保存输出内容的副本. 这种缓存带来的好处有三点: 减少网络带宽消耗, 降低服务器压力, 减少网络延迟, 加快页面打开速度, 适合请求量大, 静态的数据请求.
CDN 缓存 : 在用户和服务器之间增加 cache 层, 把数据存放到内容分发网络机房服务器中, 用户请求进从最近的 CDN 节点获取. 主要缓存图片, JS 及 CSS 文件, CDN 需要付费, 有些规模的网站才会使用.
NGINX 缓存 : 对客户已经访问过的内容在 Nginx 服务器本地建立副本, 达到减少Nginx 服务器与后端服务器之间的网络流量.
应用缓存 : 在后端应用中使用缓存, 如 java 常使用 Ehcache 及 gauva 缓存组件进行数据缓存, 也可以针对特殊场景在请求中进行线程缓存. 适合调用量大且应用内部方法间调用, 减少网络消耗.
统一缓存 : 使用内存减少对数据库的直接访问, 提高网站性能, 如使用 memcache 或 Redis 搭建缓存服务.
前四类都是在网络传输中进行数据缓存, 一般研发很少会去使用, 后两类在应用中缓存, 在开发中经常使用, 接下来介绍后两类缓存的实践案例.
实践案例
1, 热点 key
场景 : 在大促期间, 给所有活动页及频道页提供侧滑 HTML 片段数据, 会有修改.
特点 : 数据记录少, 调用量比较大(峰值 400 万 / 分钟).
在接到需求时, 第一反应是使用 Redis 进行缓存, 数据更新时删除 Redis 缓存. 读取时先读取 Redis, 缓存为空, 读取 DB 并存放 Redis.
该场景是使用 Redis 当缓存使用, 存在一定风险: 由于数据量少并发高时, 成为热点 key 会集中命中单个 Redis 实例, 流量上去后, 性能会变差, 甚至可能拖垮实例.
进一步改进本地 JVM 缓存, 加 Redis 缓存, JVM 缓存一分种失效, 回源 Redis 及数据库. 存在集中穿透缓存回源数据库, 拖垮应用或数据库的情况, 之前有过缓存失效, 集中回源数据库的经历, 结果应用服务一台台全部倒下, 数据库没有压力. 事后分析, 数据库配置最大连接数为 10, 外部请求超时时间为 500ms, 不断有新请求进来, 大量请求在等待连接. 最后选择在 JVM 使用 ConcurrentMap 存放当 DB 使用, 1 分钟异步刷新数据.
在大促当天, 页面该请求返回性能不太理想, 数据返回大概 73KB, 使用 Nginx 增加 gzip 压缩后, 数据压缩到 13KB, 性能提高不少. 后续在 Nginx 增加代理缓存, 性能稳定.
2, 类目中心设计
类目是电商领域最基础的数据, 使用依赖的系统很多, 早期是各个系统直接从数据库读取并自行缓存使用, 人为给数据库增压. 为了避免该情况, 着手搭建类目中心, 对性能及稳定要求极致, 类目中心服务异常不能影响使用方, 类目更新后要及时同步给使用方.
经过多次讨论, 确认使用三级缓存: 客户端缓存, 类目系统 jvm 缓存及统一 Redis 缓存.
▲ 类目中心 -- 读
客户端缓存 : 在对外提供的 API 依赖包中进行缓存封装, 通过调用类目系统接口提供缓存后的服务方法. 缓存数据记录失效时间, 调用时发现缓存数据已失效时, 更新失效时间并返回, 异步请求类目中心数据刷新. 若缓存没有命中, 回源请求类目中心. 客户端会定时检测类目版本信息, 若版本更新变化, 客户端数据强制更新.
类目系统 jvm 缓存 : 使用 jvm 缓存, 若有过期异步回源, 统一缓存 Redis, 穿透直接回源 Redis.
统一缓存 Redis : 当 DB 使用, 不回源数据库, 并定时从数据库把数据刷新至 Redis 中. 为了避免并发刷新, 使用 Redis 实现排它锁, 保证只一个任务刷新.
数据更新请求, 有一定的规则:
更新数据库, 保证数据库是正确数据, 后续步骤异常也可通过定时全量更新弥补;
更新 Redis 缓存;
更新类目中心所有实例 JVM 缓存: 由于系统是多实例集群, 需要通知所有实例更新 JVM 缓存;
更新版本号, 用于客户端查验强制更新标识. 一定需要 JVM 更新完成之后, 否则客户端可能获取到更新前的 "错误" 数据.
▲ 类目中心 -- 更新
客户端 95% 的请求被客户端缓存命中, 调用次数 3700 万 / 分钟, 性能 TP999 为 1ms.
▲ 客户端调用次数
▲ 客户端性能
服务端请求次数 3000 万 / 分钟也没有压力, 单实例现实际调用次数 150 万 / 分钟.
▲ 服务端调用次数
最后
缓存不仅能当缓存, 也可以当 DB 使用, 避免穿透
数据的更新分主动缓存及被动缓存
需要解决数据的一致性及有效性
如何使用, 怎么组合, 缓存什么数据, 都需要结合业务场景, 也需要一步步观察, 总结才能优化.
------------------END ------------------
下面的内容同样精彩
点击图片即可阅读
京东技术
--- 关注技术的公众号
长按识别二维码关注
来源: http://www.tuicool.com/articles/ARJ7BrE