缓存设计学习笔记

背景

最近在《 redis 开发与运维》这是在看11章时候记得笔记

存在和合理性

缓存到处都是啊

先放个最简单的图, 这里面每一层都可以有缓存
缓存设计学习笔记

client 缓存

  • 最常见的浏览器的http缓存。
  • 还有app一般也加缓存,比如陌陌,这么大活跃的情况下,不缓存服务端鸭梨太大,“更多帧”的菜单就是客户端缓存了的,每15分钟才会去刷新一次

web server

  • 最常见的nginx 对不常更新的静态资源可以在location 段里配expire来设置缓存时间; 还有nginx作为反向代理使用,可以做proxy cache
  • CDN 主要作用也是缓存,将资源缓存到里用户最近的物理机上,可以设置更新频率

业务层和DB

  • 业务逻辑处理进程内的缓存 php-fpm 有yac,java也有很多进程内缓存的库
  • Redis memcache

总之,请求的任何一个环节都可以根据需要做缓存

缓存收益

  • 加快读写速度 基于内存(redis,memcache,yac),减少数据库(磁盘)io, 内存跟磁盘的读写速度不是一个等级的哈
  • 降低后端负载 减少对后端数据的访问

成本

  • 数据的不一致性:缓存层和存储层的数据存在一定时间窗口的不一致性,时间窗口取决于更新策略
  • 代码维护成本:需要处理缓存层和存储层的逻辑
  • 运维成本: 加了一层cache,比如说redis,运维肯定是有成本的

使用场景

  • 开销大的复杂计算 如果不加缓存,每次都实时计算,无法满足高并发量
  • 加速请求响应 这个就是为了更快,内存的io比磁盘的io快

Redis 为例讲下这种缓存的设计

更新策略

剔除算法 (LRU/LFU/FIFO)
  • 使用方式 : 配置 maxmemory 和 maxmemory-policy, 对总内存进行限制,达到后按算法进行剔除
  • 场景 :这个通常是从运维角度考虑, 避免没有限制直接影响整个机器的性能; 业务层面来说肯定不能依赖这种更新策略
  • 维护成本 :开发人员只需要选择合适的算法就行,不用自己对数据进行更新
  • 一致性 :很差,你无法决定数据什么时候会过期,完全看redis本身的算法实现和触发机制
超时剔除
  • 使用方式 对key 设置过期时间
  • 场景 最常用的,只要是对一致性(实时)要求不高的都可以用,可以加快处理速度哦
  • 维护成本 :不高 用expire 设置过期时间即可,前提是允许缓存数据和真实数据在过期时间内存在不一致
  • 一致性 一段时间窗口内(就是设置的过期时间)存在不一致问题
主动更新
  • 使用场景 : 一致性要求高的地方,真实数据更新后,立刻要求缓存也更新
  • 一致性: 高; 注意的地方是主动更新万一失败,旧数据可能会很久不被更新
  • 维护成本: 高 主要开发主动更新的逻辑,并且保证更新的正确性

粒度控制

这个实际上就是缓存内容的选择问题,假设mongo里有一条完整的记录,我们是选择全部数据都缓存还是选择其中的部分数据缓存起来的问题
我们从3个角度来分析下

通用性

全部数据肯定比部分数据更通用,但很多业务场景只需要部分数据

空间占用:

全部数据显然占用更多的空间,成本更高

  • 内存还是比较昂贵的,全缓存起来可能会浪费
  • 全部数据本身如果比较大的话,在高吞吐量的情况下,对网络带宽影响也比较大,极端情况会阻塞网络
  • 大的数据结构序列号和反序列化也耗时啊
代码维护

全部数据就不用讲了。。。部分数据开发成本就比较多了

结合实践来分析下

就拿我们平台的应用信息 来说,数据非常多,基本配置几十个字段,区分ios和android的配置也各有20来个字段,

  • 当前是全部字段 json序列化后都存redis里呢
  • 实际业务开发中,你会经常发现我就为了拿1-2个字段,但是得整个读出来然后还json_decode ... 觉得好蛋疼
  • 如果redis真的是缓存层,应用信息这个单节点的redis早就挂了
  • 还好,我们的redis 基本是当存储层在用了;完整的应用信息都会有进程内缓存(php yac,java那边也加了)起来
  • 所以实际用时的经验来说,我觉得这种还是根据业务分门别类拆成多个key来存比较好,出了问题迁移和隔离方便,虽然开发成本高了点

缓存穿透

概念:不存在的数据每次请求都会去到存储层查询,并且对该数据并发请求量很大,就会对后端系统造成很大的压力,失去了缓存保护后端存储的意义。这就叫做缓存穿透

过程如下
  • 缓存层不命中
  • 存储层不命中,不将空结果填入缓存
  • 返回空结果
  • 如果出现了大量存储层的空命中,很可能就是出现了缓存穿透问题
原因分析
  • 自身的业务代码逻辑有问题,或者数据出了问题
  • 恶意攻击、爬虫等造成大量空命中
办法1 缓存空对象
  • 空值也缓存起来,就可以保护存储层了
  • 空值缓存起来,会浪费更多内存;如果是攻击,那就更惨了。通常的办法是设个比较短的过期时间,让他自动过期
  • 加了过期又需要考虑 数据不一致的问题,如果在意,还需要开发-数据有了后清除空值的逻辑
办法2 拦截
  • 把存在的key存起来,加一层拦截
  • 常用的比如布隆过滤器 我们陌陌客户端在用户资料帧的”已玩过的游戏“就用了这个拦截掉大部分请求(1.8亿/2.5亿)
上一篇:《Python数据分析》一2.2 创建多维数组


下一篇:软件测试开发实战 | 记录写装饰器时踩的几个坑