4、缓存策略基础:缓存命中与未命中、缓存替换策略(FIFO, LRU, LFU)、写策略(Write-Through, Write-Back)
聊到Tick级数据缓存,有个问题绕不开:缓存到底怎么工作?
说白了,缓存就是一块更快的存储,用来暂存热点数据。但内存就那么大,数据又那么多,你不可能把所有Tick都塞进去。这时候,命中与未命中就成了衡量缓存效率的核心指标。
4.1 缓存命中与未命中
先讲个最基础的概念。当程序要读取某个Tick数据时,如果它已经在缓存里,那就是命中(Hit)。如果不在,就得去更慢的存储(比如磁盘或数据库)里捞,这就是未命中(Miss)。
我个人习惯用命中率来评估缓存设计的好坏。命中率越高,延迟越低,系统越流畅。但要注意,命中率不是越高越好——你想想看,为了把命中率从99%提到99.9%,可能要多花好几倍的内存,这买卖划不划算,得算清楚。
核心公式:
平均访问时间 = 命中率 × 缓存访问时间 + (1 - 命中率) × 主存访问时间
这个公式我每次做缓存设计都会拿出来算一遍,心里才有底。
避坑指南:
我曾经在一个高频交易项目中,为了追求极致命中率,把缓存设得特别大。结果呢?缓存太大导致GC频繁,反而拖慢了整体性能。后来我学乖了,命中率够用就行,别贪。
4.2 缓存替换策略
缓存满了怎么办?得把旧数据踢出去,腾地方给新数据。这就是替换策略的活儿。常见的策略有三种:FIFO、LRU、LFU。我一个个说。
4.2.1 FIFO(先进先出)
FIFO的思路很简单:谁先来,谁先走。就像一个排队买奶茶的队伍,先排的先买到,买完就走。
但FIFO有个致命问题:它不考虑数据的访问频率。你想想看,一个高频访问的热点数据,可能因为来得早,就被无情地踢出去了。这在Tick级数据场景下,简直是灾难。
注意:FIFO在Tick数据缓存中几乎不单独使用。我见过有人用它做冷数据淘汰,但效果一般。除非你的数据访问模式非常均匀,否则别碰FIFO。
4.2.2 LRU(最近最少使用)
LRU是目前最常用的策略。它的逻辑是:如果数据最近被访问过,那么将来被访问的概率也高。所以,淘汰时优先淘汰最久没被访问的那个。
我在项目中用过很多次LRU,效果确实不错。但要注意实现成本——标准的LRU需要维护一个双向链表,每次访问都要移动节点,这在Tick级数据的高并发场景下,锁竞争会很严重。
// 伪代码:LRU缓存实现思路
class LRUCache {
// 双向链表 + 哈希表
// 每次访问,把节点移到链表头部
// 淘汰时,移除链表尾部节点
}
我的经验:如果并发量特别大,可以考虑用近似LRU,比如Clock算法。它用标志位模拟访问顺序,性能更好,命中率也差不太多。
4.2.3 LFU(最不经常使用)
LFU看的是访问频率。谁被访问的次数最少,谁就先被淘汰。听起来很合理对吧?但LFU有个坑:频率老化问题。
举个例子:一个数据昨天被访问了1000次,今天一次都没被访问。按照LFU,它因为历史频率高,一直赖在缓存里不走。而今天新来的热点数据,反而被挤出去了。这显然不合理。
解决方案:我一般用衰减LFU,定期给所有数据的访问计数打个折扣(比如乘以0.9),让历史数据慢慢“过期”。这样既能保留热点,又能适应访问模式的变化。
4.3 写策略
缓存不光要读,还要写。写策略决定了数据写入缓存时,怎么同步到后端存储。主要有两种:Write-Through和Write-Back。
4.3.1 Write-Through(写直达)
Write-Through的策略是:数据写入缓存的同时,也写入后端存储。这样缓存和存储始终保持一致,不会丢数据。
但代价也很明显:每次写操作都要等后端存储写完才能返回,延迟高。在Tick级数据场景下,如果写操作频繁,Write-Through会拖慢整个系统。
注意:Write-Through适合写少读多的场景。如果写操作占比高,建议别用。
4.3.2 Write-Back(写回)
Write-Back就聪明多了:数据先写入缓存,标记为“脏数据”,等缓存块被淘汰时,再写回后端存储。
这样做的好处是写操作很快,因为只写缓存。但风险也大:如果缓存还没写回存储就宕机了,数据就丢了。
我的做法:在Tick级数据缓存中,我通常用Write-Back + 定期刷盘。比如每100毫秒强制把脏数据写回一次。这样既保证了性能,又控制了数据丢失的风险。
4.4 知识体系图
下面这张图,我把缓存策略的核心逻辑串起来了。你看一眼,应该就能明白它们之间的关系。
总结一下:
- 缓存命中率是核心指标,但别盲目追求100%
- 替换策略:Tick级数据首选LRU,并发高用近似LRU
- 写策略:Write-Back性能好,但要注意数据安全
- 没有银弹,根据业务场景选型才是王道
最后说一句:缓存策略没有绝对的好坏,只有合不合适。我见过有人用FIFO做日志缓存,效果出奇的好。也见过有人用LRU做Tick缓存,结果因为锁竞争导致性能还不如不用缓存。所以,动手测一测,比什么都强。
公众号:蓝海数据掘金营,微信deep3321