HiSEN

缓存那些事儿 - 从组件到实践

一、为什么用缓存

1.1 空间换时间:

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

1.2 局部性原理:

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

二、缓存组件介绍

2.1 Ehcache

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

2.2 Guava

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

2.3 Memcache

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

2.4 Redis

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

三、正确使用缓存

3.1 读场景

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

3.2 写场景

cache 指的是删除『缓存』
db 指的是『更新/删除数据库』

3.2.1 先 cache 后 db

删除缓存成功,更新数据库失败,不影响数据准确性。
如果在 cache 与 db 短暂的时间内
有访问查询动作(先查缓存,后查 db 并且设置缓存)
那么还缓存中还是会存在过期的数据

3.2.2 先 db 后 cache

如果 db 成功,cache 失败,会导致数据不一致。
可以让 db cache 在一个事务,cache 失败回滚 db,保证一致性。

3.2.3 方案总结

其实本质上就是个分布式事务问题
怎么保证两个操作同时成功/失败
通过本地事务 / 补偿机制 实现会比较好

四、缓存问题集合

4.1 缓存穿透

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

4.2 缓存雪崩

缓存高可用(集群+主备)
循环一致性 Hash,节点组成一个环,如果一个节点挂了,顺着往下走;

五、参考链接

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