现代的消息队列,都是用磁盘来存储的,磁盘具有持久化存储的能力

但是磁盘的速度慢,为了解决这个慢的问题,我们往往利用内存来加速应用服务的访问速度

那么我们就讨论下缓存的使用

1.只读缓存还是读写缓存

Kafka就是一种典型的读写缓存,利用的就是操作系统的PageCache

但是,PageCache在使用过程中,具有一定的延迟,是天然不可靠的,是一种牺牲数据一致性换取性能的设计

当然,应用程序可以使用sync等系统调用,强制系统将缓存同步到磁盘文件中

所以不建议使用读写缓存

但为何Kakfa为何使用PageCache来提升性能呢?

这是因为Kafka的数据结构决定的

Kafka并不只是利用磁盘来保证数据的可靠性,而是不同节点上的多副本来解决数据可靠性,即使服务器丢失一部分的数据,也可以利用其它的节点来找到正确的数据

但是对于大部分的消息队列,读写比是不均衡,一般读的频率高于写的频率,所以,应该考虑只读缓存来进行加速系统

只读缓存来说,数据都会从磁盘上更新,分布式系统中,除非是使用事务或者分布式一致性算法保证,不然还是无法保证缓存中的数据和磁盘中的一致

比如说

如何选择同步还是异步更新缓存,同步更新,如果更新磁盘成功,但是缓存失败,是否无限重试?

如果异步的话,怎么保证更新的时序性?

虽然对于某些场景,比如头像,更新的比较慢,也是可以接受的,但是大部分场景,对数据的一致性也是有要求的,对于一些极端场景,我们甚至可以选择不更新缓存

缓存置换策略

对于使用缓存,我们需要考虑,如何保证缓存的命中率

如果要使用某些数据的时候,如果其在缓存汇总,直接访问缓存就可以了

如果没有,就只能去磁盘中读取,这也是一种缓存穿透,

如何避免缓存穿透和提升命中率

一般来说,能够直接定制化的是最好的,比如一个系统会话的系统,能知道哪些用户在线,哪些用户离线,优先保证在线用户的数据也是一个好的策略

常见的就是LRU算法,可以将长时间没有访问的户数置换出去

Kakfa使用的PageCache,是由Linux内核实现的一种LRU算法

LRU 2Q,淘汰最少使用的页,然后根据消息这种流数据存储的特点,淘汰的增加一个考量维度,页面位置与尾部的距离,越靠近尾部数据,访问概率就越大

但是需要考虑挖坟的概念,避免读取历史数据,导致内存的缓存都被替换为了历史数据

课后问题,实现一个LRU置换缓存的代码

/**

* KV存储抽象

*/

public interface Storage<K,V> {

/**

* 根据提供的key来访问数据

* @param key 数据Key

* @return 数据值

*/

V get(K key);

}

/**

* LRU缓存。你需要继承这个抽象类来实现LRU缓存。

* @param <K> 数据Key

* @param <V> 数据值

*/

public abstract class LruCache<K, V> implements Storage<K,V>{

// 缓存容量

protected final int capacity;

// 低速存储,所有的数据都可以从这里读到

protected final Storage<K,V> lowSpeedStorage;

public LruCache(int capacity, Storage<K,V> lowSpeedStorage) {

this.capacity = capacity;

this.lowSpeedStorage = lowSpeedStorage;

}

}

发表评论

邮箱地址不会被公开。 必填项已用*标注