✅如果设计一个缓存,需要考虑哪些方面?
典型回答
缓存分为本地缓存和分布式缓存,如果是本地缓存,需要考虑的问题有:
数据结构、线程安全、对象上限、清除策略、过期时间等。
数据结构
一般来讲,为了提升缓存的效率,通常采用Key-Value结构进行数据存储,也就是说,缓存中的数据保存和读取都需要有一个Key,通过Key来读取固定的缓存的Value。
线程安全
本地缓存一定要考虑线程安全的问题,因为大多数情况下本地缓存都是一个全局可访问的变量,那么就会有多个线程同时访问,所以线程安全问题不容忽视。
对象上限
因为是本地缓存,而本地内存中的数据是要占用JVM的堆内存的,所以内存是有上限要求的,如果无限存储,最终一定会导致OOM的问题。
清除策略
为了避免OOM的问题,一般会考虑在缓存中增加清除策略,通过一定的手段定期的清理掉一些数据,来保证内存占用不会过大,常见清除策略主要有有LRU(最近最少使用)、FIFO(先进先出)、LFU(最近最不常用)、SOFT(软引用)、WEAK(弱引用)等;
过期时间
有了清除策略并不能保证百分百的可以删除数据,极端情况会会使得某些数据一直无法删除。这时候就需要有一种机制,能够保证某些K-V一定可以删除。通常采用的方案是给每一个缓存的key设置过期时间,当达到过期时间之后直接删除,采用清除策略+过期时间双重保证;
以下是Caffeine这个优秀的缓存框架在这几个方面的做法:
数据结构:Caffeine 的核心数据结构基于 **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">ConcurrentHashMap</font>** 和 时间轮(Timer Wheel) 实现高效缓存管理。通过锁分段技术减少锁竞争,每个 Segment 独立管理一部分哈希桶,提升并发读写性能。
线程安全:Caffeine在关键路径(如缓存命中计数、频率统计)中使用 CAS(Compare-and-Swap)操作,减少锁开销。并且实用**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">ConcurrentHashMap</font>** 这种线程安全的数据结构。
对象上限:通过 maximumSize(long size) 设置缓存最大条目数。通过 weigher((k, v) -> weight) 自定义权重计算,配合 maximumWeight(long weight) 限制总权重。
清除策略:Caffeine 的淘汰策略是其性能优势的核心,采用 Window TinyLFU 算法,结合 LRU 和 LFU 的优点。
过期时间:Caffeine 支持多种过期策略,并通过时间轮高效管理。
如果是分布式缓存,在本地缓存的基础上,还需要考虑持久化机制、集群模式等。以下是Redis这个优秀的缓存框架在这几个方面的做法:
数据结构:https://www.yuque.com/hollis666/sza8tg/hlg4u2
线程安全:https://www.yuque.com/hollis666/sza8tg/og6nf4
清除策略:https://www.yuque.com/hollis666/sza8tg/xw99lcraocebx1mk
持久化机制:https://www.yuque.com/hollis666/sza8tg/zc5q70
过期策略:https://www.yuque.com/hollis666/sza8tg/ggsht0
集群模式:https://www.yuque.com/hollis666/sza8tg/namhuv165lorwudw