【Redis】缓存设计

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Francis123580/article/details/82555453

缓存的收益和成本

收益

  1. 加速读写:缓存是全内存的,可以加速读写,优化用户体验
  2. 降低后端负载:减少后端访问量和复杂计算,降低了负载

成本

  1. 数据不一致:缓存层和存储层的数据存在着一定时间窗口的不一致性,时间窗口和更新策略有关
  2. 代码维护成本:需要同时处理缓存层和存储层的逻辑
  3. 运维成本:例如Redis Cluster,增大了运维成本

缓存更新策略

LRU / LFU / FIFO 算法剔除

使用场景:缓存使用量超过了预设的最大值,根据相应算法进行剔除

一致性:数据清理由算法决定,人工无法干预,一致性最差

维护成本:只需配置 maxmemory 和 对应策略,维护成本低


超时剔除

使用场景:给缓存数据设置过期时间,过期后自动删除

一致性:一段时间窗口内存在一致性问题

维护成本:只需设置过期时间,维护成本不高


主动更新

使用场景:应用方对于数据的一致性要求高,需要在真实数据更新后,立即更新缓存数据

一致性:一致性最高,可以配合超时剔除使用效果会更好

维护成本:维护成本比较高,开发者自己完成更新,并保证更新操作的正确性


最佳实践

  • 低一致性业务:建议使用算法剔除
  • 高一致性业务:建议使用主动更新和超时剔除,即使主动更新出了问题,数据过期后也可以删除脏数据

缓存粒度控制

数据类型 通用性 空间占用(内存空间 + 网络带宽) 代码维护
全部数据 简单
部分数据 较为复杂

缓存粒度设计 要根据通用性、空间占用、代码维护 综合考虑,在这几者进行取舍

穿透优化

缓存穿透

概念:查询一个根本不存在的数据,缓存层和存储层都不会命中,通常处于容错的考虑,从存储层查不到的数据则不写入缓存层

后果:导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储层的意义

原因:1.自身业务代码或者数据有问题;2.恶意攻击、爬虫


解决方案

1.缓存空对象:第一次存储层命中后将空对象缓存到缓存层中

  • 空间占用:如果缓存层存入大量键,占用大量空间,可以通过设置一个较短的过期时间,自动剔除
  • 数据不一致:存储添加了数据后,可以利用消息系统或者其他方式清楚缓存中的空对象

2.布隆过滤器拦截:在缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来

  • 布隆过滤器拦截代码维护复杂,但是缓存空间占用少

无底洞优化

无底洞现象

为了满足业务需要添加了大量新节点,但是性能没有好转反而下降了

无底洞分析

  • 客户端一次批量操作会涉及多次网络操作,耗时会不断加大
  • 网络连接数变多,对节点的性能也有一定影响

优化策略

  1. 串行命令:将megt命令变成n次get命令,但延迟严重
  2. 串行IO:获取每个节点key,对每个节点进行mget或pipeline操作,但延迟严重
  3. 并行IO:将串行IO最后获取数据变成多线程并行化,但编程复杂
  4. hash_tag 实现:将多个key强行分配到一个节点,但容易出现数据倾斜

雪崩优化

雪崩概念

缓存层由于某些原因不能提供服务,所有请求都会到达存储层,造成级联宕机

预防和解决

  1. 保证缓存层服务高可用性:Redis Sentinel 和 Redis Cluster
  2. 依赖隔离组件为后端限流并降级:对资源进行隔离,都单独运行自己的线程池中
  3. 提前演练:项目上线前提前演练缓存宕掉后出现的问题

热点 key 重建优化

热点key问题

当前key是一个热点key,并发量非常大,但是重建缓存不能在短时间完成,在缓存失效的瞬间,大量线程重建缓存,造成后端压力加大

解决

  1. 互斥锁:只允许一个线程重建缓存,其它线程等待执行完从缓存获取数据即可
  2. 永远不过期:不设置过期时间;或 从逻辑上用单独线程定期构建缓存

潜在问题

  1. 互斥锁代码复杂度大,存在死锁风险,存在线程池阻塞风险
  2. 永不过期 不能保证一致性,逻辑过期时间增加了代码维护成本和内存成本

猜你喜欢

转载自blog.csdn.net/Francis123580/article/details/82555453