HiSEN

缓存那些事儿 - 本地缓存(ecache|guava)|分布式缓存(memcache|redis)

ps:时间比较仓促,写的比较粗糙,后期再优化;

一、为什么用缓存

1.1 空间换时间:

缓存是针对读多写少的场景典型的以空间换时间的操作
空间:内存
时间:读内存速度快(相对于读磁盘)

1.2 局部性原理:

这个世界很多事情都符合28原则
把热点数据缓存起来就大大提高系统效率

二、缓存组件选择

2.1 Ecache

  1. 快速,针对大型高并发系统场景,Ehcache的多线程机制有相应的优化改善;
  2. 简单,很小的jar包,简单配置就可直接使用,单机场景下无需过多的其他服务依赖;
  3. 支持多种的缓存策略,灵活;
  4. 缓存数据有两级:内存和磁盘,与一般的本地内存缓存相比,有了磁盘的存储空间,将可以支持更大量的数据缓存需求;
  5. 具有缓存和缓存管理器的侦听接口,能更简单方便的进行缓存实例的监控管理;
  6. 支持多缓存管理器实例,以及一个实例的多个缓存区域;

2.2 Guava

  1. 自动将entry节点加载进缓存结构中;
  2. 当缓存的数据超过设置的最大值时,使用LRU算法移除;
  3. 具备根据entry节点上次被访问或者写入时间计算它的过期机制;
  4. 缓存的key被封装在WeakReference引用内;
  5. 缓存的Value被封装在WeakReference或SoftReference引用内;
  6. 统计缓存使用过程中命中率、异常率、未命中率等统计数据;

2.3 Memcache

  1. memcache使用预分配内存池的方式管理内存;
  2. 所有数据存储在物理内存里;
  3. 非阻塞IO复用模型,纯KV存取操作;
  4. 多线程,效率高,会遇到锁等,上下文切换问题;
  5. 只支持简单KV数据类型;
  6. 数据不支持持久化;

2.4 Redis

  1. 临时申请空间,可能导致碎片;
  2. 有VM机制,能存储更多数据,超过内存空间后会导致swap,降低效率;
  3. 非阻塞IO复用模型,支持额外CPU计算:排序、聚合,会影响IO性能;
  4. 单线程,无锁,无上下文切换,单实例无法利用多核性能;
  5. 支持多种数据类型:string / hash / list / set / sorted set
  6. 数据支持持久化:AOF(语句增量)/RDB(fork全量)
  7. 天然支持高可用分布式方案sentinel + cluster(故障自动转移+集群)

三、正确使用缓存

3.1 读场景:先读缓存、再读DB

如果是并发读缓存失效,使用分布式锁只允许单次查询,其它等待,超时返回失败

3.2 写场景:先delete缓存、再更新数据库

当删除缓存失败,直接返回异常
当写数据库失败,返回异常
不会导致数据不一致问题

四、缓存问题集合

4.1 数据库主从结构不一致导致

解决方案①:从同步完成之后,删除对应key的缓存,又短暂数据不一致问题;
解决方案②:不一致期间,强制读取主库,upd某数据时,把key放在缓存标识有更新,读取操作看到有更新标识强制读主库

4.2 缓存穿透

解决方案:当查询到某数据不存在时,缓存假数据,例如:(key,key#);

4.3 缓存雪崩

解决方案:缓存高可用(集群+主备),循环一致性Hahs方案(打的会比较均匀,不至于一个节点出问题,多米乐骨牌效应压垮所有数据)

五、参考链接

  1. 架构师之路18年精选100篇 | 缓存部分 - 58沈剑
  2. 缓存那些事 | 美团技术博客